diff --git a/core/crypto/ed25519/ed25519.odin b/core/crypto/ed25519/ed25519.odin new file mode 100644 index 00000000000..18e42a5998f --- /dev/null +++ b/core/crypto/ed25519/ed25519.odin @@ -0,0 +1,298 @@ +/* +package ed25519 implements the Ed25519 EdDSA signature algorithm. + +See: +- https://datatracker.ietf.org/doc/html/rfc8032 +- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf +- https://eprint.iacr.org/2020/1244.pdf +*/ +package ed25519 + +import "core:crypto" +import grp "core:crypto/_edwards25519" +import "core:crypto/sha2" +import "core:mem" + +// PRIVATE_KEY_SIZE is the byte-encoded private key size. +PRIVATE_KEY_SIZE :: 32 +// PUBLIC_KEY_SIZE is the byte-encoded public key size. +PUBLIC_KEY_SIZE :: 32 +// SIGNATURE_SIZE is the byte-encoded signature size. +SIGNATURE_SIZE :: 64 + +@(private) +NONCE_SIZE :: 32 + +// Private_Key is an Ed25519 private key. +Private_Key :: struct { + // WARNING: All of the members are to be treated as internal (ie: + // the Private_Key structure is intended to be opaque). There are + // subtle vulnerabilities that can be introduced if the internal + // values are allowed to be altered. + // + // See: https://github.com/MystenLabs/ed25519-unsafe-libs + _b: [PRIVATE_KEY_SIZE]byte, + _s: grp.Scalar, + _nonce: [NONCE_SIZE]byte, + _pub_key: Public_Key, + _is_initialized: bool, +} + +// Public_Key is an Ed25519 public key. +Public_Key :: struct { + // WARNING: All of the members are to be treated as internal (ie: + // the Public_Key structure is intended to be opaque). + _b: [PUBLIC_KEY_SIZE]byte, + _neg_A: grp.Group_Element, + _is_valid: bool, + _is_initialized: bool, +} + +// private_key_set_bytes decodes a byte-encoded private key, and returns +// true iff the operation was successful. +private_key_set_bytes :: proc(priv_key: ^Private_Key, b: []byte) -> bool { + if len(b) != PRIVATE_KEY_SIZE { + return false + } + + // Derive the private key. + ctx: sha2.Context_512 = --- + h_bytes: [sha2.DIGEST_SIZE_512]byte = --- + sha2.init_512(&ctx) + sha2.update(&ctx, b) + sha2.final(&ctx, h_bytes[:]) + + copy(priv_key._b[:], b) + copy(priv_key._nonce[:], h_bytes[32:]) + grp.sc_set_bytes_rfc8032(&priv_key._s, h_bytes[:32]) + + // Derive the corresponding public key. + A: grp.Group_Element = --- + grp.ge_scalarmult_basepoint(&A, &priv_key._s) + grp.ge_bytes(&A, priv_key._pub_key._b[:]) + grp.ge_negate(&priv_key._pub_key._neg_A, &A) + priv_key._pub_key._is_valid = !grp.ge_is_small_order(&A) + priv_key._pub_key._is_initialized = true + + priv_key._is_initialized = true + + return true +} + +// private_key_bytes sets dst to byte-encoding of priv_key. +private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) { + if !priv_key._is_initialized { + panic("crypto/ed25519: uninitialized private key") + } + if len(dst) != PRIVATE_KEY_SIZE { + panic("crypto/ed25519: invalid destination size") + } + + copy(dst, priv_key._b[:]) +} + +// private_key_clear clears priv_key to the uninitialized state. +private_key_clear :: proc "contextless" (priv_key: ^Private_Key) { + mem.zero_explicit(priv_key, size_of(Private_Key)) +} + +// sign writes the signature by priv_key over msg to sig. +sign :: proc(priv_key: ^Private_Key, msg, sig: []byte) { + if !priv_key._is_initialized { + panic("crypto/ed25519: uninitialized private key") + } + if len(sig) != SIGNATURE_SIZE { + panic("crypto/ed25519: invalid destination size") + } + + // 1. Compute the hash of the private key d, H(d) = (h_0, h_1, ..., h_2b-1) + // using SHA-512 for Ed25519. H(d) may be precomputed. + // + // 2. Using the second half of the digest hdigest2 = hb || ... || h2b-1, + // define: + // + // 2.1 For Ed25519, r = SHA-512(hdigest2 || M); Interpret r as a + // 64-octet little-endian integer. + ctx: sha2.Context_512 = --- + digest_bytes: [sha2.DIGEST_SIZE_512]byte = --- + sha2.init_512(&ctx) + sha2.update(&ctx, priv_key._nonce[:]) + sha2.update(&ctx, msg) + sha2.final(&ctx, digest_bytes[:]) + + r: grp.Scalar = --- + grp.sc_set_bytes_wide(&r, &digest_bytes) + + // 3. Compute the point [r]G. The octet string R is the encoding of + // the point [r]G. + R: grp.Group_Element = --- + R_bytes := sig[:32] + grp.ge_scalarmult_basepoint(&R, &r) + grp.ge_bytes(&R, R_bytes) + + // 4. Derive s from H(d) as in the key pair generation algorithm. + // Use octet strings R, Q, and M to define: + // + // 4.1 For Ed25519, digest = SHA-512(R || Q || M). + // Interpret digest as a little-endian integer. + sha2.init_512(&ctx) + sha2.update(&ctx, R_bytes) + sha2.update(&ctx, priv_key._pub_key._b[:]) // Q in NIST terminology. + sha2.update(&ctx, msg) + sha2.final(&ctx, digest_bytes[:]) + + sc: grp.Scalar = --- // `digest` in NIST terminology. + grp.sc_set_bytes_wide(&sc, &digest_bytes) + + // 5. Compute S = (r + digest × s) mod n. The octet string S is the + // encoding of the resultant integer. + grp.sc_mul(&sc, &sc, &priv_key._s) + grp.sc_add(&sc, &sc, &r) + + // 6. Form the signature as the concatenation of the octet strings + // R and S. + grp.sc_bytes(sig[32:], &sc) + + grp.sc_clear(&r) +} + +// public_key_set_bytes decodes a byte-encoded public key, and returns +// true iff the operation was successful. +public_key_set_bytes :: proc "contextless" (pub_key: ^Public_Key, b: []byte) -> bool { + if len(b) != PUBLIC_KEY_SIZE { + return false + } + + A: grp.Group_Element = --- + if !grp.ge_set_bytes(&A, b) { + return false + } + + copy(pub_key._b[:], b) + grp.ge_negate(&pub_key._neg_A, &A) + pub_key._is_valid = !grp.ge_is_small_order(&A) + pub_key._is_initialized = true + + return true +} + +// public_key_set_priv sets pub_key to the public component of priv_key. +public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) { + if !priv_key._is_initialized { + panic("crypto/ed25519: uninitialized public key") + } + + src := &priv_key._pub_key + copy(pub_key._b[:], src._b[:]) + grp.ge_set(&pub_key._neg_A, &src._neg_A) + pub_key._is_valid = src._is_valid + pub_key._is_initialized = src._is_initialized +} + +// public_key_bytes sets dst to byte-encoding of pub_key. +public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) { + if !pub_key._is_initialized { + panic("crypto/ed25519: uninitialized public key") + } + if len(dst) != PUBLIC_KEY_SIZE { + panic("crypto/ed25519: invalid destination size") + } + + copy(dst, pub_key._b[:]) +} + +// public_key_equal returns true iff pub_key is equal to other. +public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool { + if !pub_key._is_initialized || !other._is_initialized { + panic("crypto/ed25519: uninitialized public key") + } + + return crypto.compare_constant_time(pub_key._b[:], other._b[:]) == 1 +} + +// verify returns true iff sig is a valid signature by pub_key over msg. +verify :: proc(pub_key: ^Public_Key, msg, sig: []byte) -> bool { + switch { + case !pub_key._is_initialized: + return false + case len(sig) != SIGNATURE_SIZE: + return false + } + + // TLDR: Just use ristretto255. + // + // While there are two "standards" for EdDSA, existing implementations + // diverge (sometimes dramatically). This implementation opts for + // "Algorithm 2" from "Taming the Many EdDSAs", which provides the + // strongest notion of security (SUF-CMA + SBS). + // + // The relevant properties are: + // - Reject non-canonical S. + // - Reject non-canonical A/R. + // - Reject small-order A (Extra non-standard check). + // - Cofactored verification equation. + // + // There are 19 possible non-canonical group element encodings of + // which: + // - 2 are small order (possible but unlikely for R) + // - 10 are mixed order (NEVER produced by a valid sign implementation) + // - 7 are not on the curve + // + // There are 8 small-order group elements, 1 which is in the + // prime-order sub-group, and thus the probability that A is + // small-order is cryptographically insignificant. + // + // While both the RFC and FIPS standard allow for either the + // cofactored or non-cofactored equation. It is possible to + // artificially produce signatures that are valid for one but + // not the other. This will NEVER occur with a valid sign + // implementation. The choice of the latter is to be compatible + // with things like batch verification and FROST. + + s_bytes, r_bytes := sig[32:], sig[:32] + + // 1. Reject the signature if S is not in the range [0, L). + s: grp.Scalar = --- + if !grp.sc_set_bytes(&s, s_bytes) { + return false + } + + // 2. Reject the signature if the public key A is one of 8 small + // order points. + if !pub_key._is_valid { + return false + } + + // 3. Reject the signature if A or R are non-canonical. + // + // Note: All initialized public keys are guaranteed to be canonical. + neg_R: grp.Group_Element = --- + if !grp.ge_set_bytes(&neg_R, r_bytes) { + return false + } + grp.ge_negate(&neg_R, &neg_R) + + // 4. Compute the hash SHA512(R||A||M) and reduce it mod L to get a + // scalar h. + ctx: sha2.Context_512 = --- + h_bytes: [sha2.DIGEST_SIZE_512]byte = --- + sha2.init_512(&ctx) + sha2.update(&ctx, r_bytes) + sha2.update(&ctx, pub_key._b[:]) + sha2.update(&ctx, msg) + sha2.final(&ctx, h_bytes[:]) + + h: grp.Scalar = --- + grp.sc_set_bytes_wide(&h, &h_bytes) + + // 5. Accept if 8(s * G) - 8R - 8(h * A) = 0 + // + // > first compute V = SB − R − hA and then accept if V is one of + // > 8 small order points (or alternatively compute 8V with 3 + // > doublings and check against the neutral element) + V: grp.Group_Element = --- + grp.ge_double_scalarmult_basepoint_vartime(&V, &h, &pub_key._neg_A, &s) + grp.ge_add(&V, &V, &neg_R) + + return grp.ge_is_small_order(&V) +} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index cc0005840ac..f60088823f1 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -29,6 +29,7 @@ import blake2s "core:crypto/blake2s" import chacha20 "core:crypto/chacha20" import chacha20poly1305 "core:crypto/chacha20poly1305" import crypto_hash "core:crypto/hash" +import ed25519 "core:crypto/ed25519" import hkdf "core:crypto/hkdf" import hmac "core:crypto/hmac" import kmac "core:crypto/kmac" @@ -152,6 +153,7 @@ _ :: blake2b _ :: blake2s _ :: chacha20 _ :: chacha20poly1305 +_ :: ed25519 _ :: hmac _ :: hkdf _ :: kmac