Skip to content

Commit

Permalink
core/crypto/ed25519: Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
Yawning committed Apr 8, 2024
1 parent 3799d39 commit cf4b5da
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 0 deletions.
298 changes: 298 additions & 0 deletions core/crypto/ed25519/ed25519.odin
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 2 additions & 0 deletions examples/all/all_main.odin
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -152,6 +153,7 @@ _ :: blake2b
_ :: blake2s
_ :: chacha20
_ :: chacha20poly1305
_ :: ed25519
_ :: hmac
_ :: hkdf
_ :: kmac
Expand Down

0 comments on commit cf4b5da

Please sign in to comment.