mirror of
https://github.com/odin-lang/Odin.git
synced 2026-02-12 06:18:39 +00:00
core/crypto/ecdh: Initial import
This commit is contained in:
4
core/crypto/ecdh/doc.odin
Normal file
4
core/crypto/ecdh/doc.odin
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
A generic interface to Elliptic Curve Diffie-Hellman key exchange.
|
||||
*/
|
||||
package ecdh
|
||||
404
core/crypto/ecdh/ecdh.odin
Normal file
404
core/crypto/ecdh/ecdh.odin
Normal file
@@ -0,0 +1,404 @@
|
||||
package ecdh
|
||||
|
||||
import "core:crypto"
|
||||
import secec "core:crypto/_weierstrass"
|
||||
import "core:crypto/x25519"
|
||||
import "core:crypto/x448"
|
||||
import "core:mem"
|
||||
import "core:reflect"
|
||||
|
||||
// Note: For these primitives scalar size = point size
|
||||
@(private="file")
|
||||
X25519_Buf :: [x25519.SCALAR_SIZE]byte
|
||||
@(private="file")
|
||||
X448_Buf :: [x448.SCALAR_SIZE]byte
|
||||
|
||||
// Curve the curve identifier associated with a given Private_Key
|
||||
// or Public_Key
|
||||
Curve :: enum {
|
||||
Invalid,
|
||||
SECP256R1,
|
||||
X25519,
|
||||
X448,
|
||||
}
|
||||
|
||||
// CURVE_NAMES is the Curve to curve name string.
|
||||
CURVE_NAMES := [Curve]string {
|
||||
.Invalid = "Invalid",
|
||||
.SECP256R1 = "secp256r1",
|
||||
.X25519 = "X25519",
|
||||
.X448 = "X448",
|
||||
}
|
||||
|
||||
// PRIVATE_KEY_SIZES is the Curve to private key size in bytes.
|
||||
PRIVATE_KEY_SIZES := [Curve]int {
|
||||
.Invalid = 0,
|
||||
.SECP256R1 = secec.SC_SIZE_P256R1,
|
||||
.X25519 = x25519.SCALAR_SIZE,
|
||||
.X448 = x448.SCALAR_SIZE,
|
||||
}
|
||||
|
||||
// PUBLIC_KEY_SIZES is the Curve to public key size in bytes.
|
||||
PUBLIC_KEY_SIZES := [Curve]int {
|
||||
.Invalid = 0,
|
||||
.SECP256R1 = 1 + 2 * secec.FE_SIZE_P256R1,
|
||||
.X25519 = x25519.POINT_SIZE,
|
||||
.X448 = x448.POINT_SIZE,
|
||||
}
|
||||
|
||||
// SHARED_SECRET_SIZES is the Curve to shared secret size in bytes.
|
||||
SHARED_SECRET_SIZES := [Curve]int {
|
||||
.Invalid = 0,
|
||||
.SECP256R1 = secec.FE_SIZE_P256R1,
|
||||
.X25519 = x25519.POINT_SIZE,
|
||||
.X448 = x448.POINT_SIZE,
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
_PRIV_IMPL_IDS := [Curve]typeid {
|
||||
.Invalid = nil,
|
||||
.SECP256R1 = typeid_of(secec.Scalar_p256r1),
|
||||
.X25519 = typeid_of(X25519_Buf),
|
||||
.X448 = typeid_of(X448_Buf),
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
_PUB_IMPL_IDS := [Curve]typeid {
|
||||
.Invalid = nil,
|
||||
.SECP256R1 = typeid_of(secec.Point_p256r1),
|
||||
.X25519 = typeid_of(X25519_Buf),
|
||||
.X448 = typeid_of(X448_Buf),
|
||||
}
|
||||
|
||||
// Private_Key is an ECDH 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).
|
||||
_curve: Curve,
|
||||
_impl: union {
|
||||
secec.Scalar_p256r1,
|
||||
X25519_Buf,
|
||||
X448_Buf,
|
||||
},
|
||||
_pub_key: Public_Key,
|
||||
}
|
||||
|
||||
// Public_Key is an ECDH 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).
|
||||
_curve: Curve,
|
||||
_impl: union {
|
||||
secec.Point_p256r1,
|
||||
X25519_Buf,
|
||||
X448_Buf,
|
||||
},
|
||||
}
|
||||
|
||||
// private_key_generate uses the system entropy source to generate a new
|
||||
// Private_Key. This will only fail iff the system entropy source is
|
||||
// missing or broken.
|
||||
private_key_generate :: proc(priv_key: ^Private_Key, curve: Curve) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
return false
|
||||
}
|
||||
|
||||
reflect.set_union_variant_typeid(
|
||||
priv_key._impl,
|
||||
_PRIV_IMPL_IDS[curve],
|
||||
)
|
||||
|
||||
#partial switch curve {
|
||||
case .SECP256R1:
|
||||
sc := &priv_key._impl.(secec.Scalar_p256r1)
|
||||
|
||||
// 384-bits reduced makes the modulo bias insignificant
|
||||
b: [48]byte = ---
|
||||
defer (mem.zero_explicit(&b, size_of(b)))
|
||||
for {
|
||||
crypto.rand_bytes(b[:])
|
||||
_ = secec.sc_set_bytes(sc, b[:])
|
||||
if secec.sc_is_zero(sc) == 0 { // Likely
|
||||
break
|
||||
}
|
||||
}
|
||||
case .X25519:
|
||||
sc := &priv_key._impl.(X25519_Buf)
|
||||
crypto.rand_bytes(sc[:])
|
||||
case .X448:
|
||||
sc := &priv_key._impl.(X448_Buf)
|
||||
crypto.rand_bytes(sc[:])
|
||||
case:
|
||||
panic("crypto/ecdh: invalid curve")
|
||||
}
|
||||
|
||||
priv_key._curve = curve
|
||||
private_key_generate_public(priv_key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 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, curve: Curve, b: []byte) -> bool {
|
||||
private_key_clear(priv_key)
|
||||
|
||||
if len(b) != PRIVATE_KEY_SIZES[curve] {
|
||||
return false
|
||||
}
|
||||
|
||||
reflect.set_union_variant_typeid(
|
||||
priv_key._impl,
|
||||
_PRIV_IMPL_IDS[curve],
|
||||
)
|
||||
|
||||
#partial switch curve {
|
||||
case .SECP256R1:
|
||||
sc := &priv_key._impl.(secec.Scalar_p256r1)
|
||||
did_reduce := secec.sc_set_bytes(sc, b)
|
||||
is_zero := secec.sc_is_zero(sc) == 1
|
||||
|
||||
// Reject `0` and scalars that are not less than the
|
||||
// curve order.
|
||||
if did_reduce || is_zero {
|
||||
private_key_clear(priv_key)
|
||||
return false
|
||||
}
|
||||
case .X25519:
|
||||
sc := &priv_key._impl.(X25519_Buf)
|
||||
copy(sc[:], b)
|
||||
case .X448:
|
||||
sc := &priv_key._impl.(X448_Buf)
|
||||
copy(sc[:], b)
|
||||
case:
|
||||
panic("crypto/ecdh: invalid curve")
|
||||
}
|
||||
|
||||
priv_key._curve = curve
|
||||
private_key_generate_public(priv_key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
private_key_generate_public :: proc(priv_key: ^Private_Key) {
|
||||
switch &sc in priv_key._impl {
|
||||
case secec.Scalar_p256r1:
|
||||
pub_key: secec.Point_p256r1 = ---
|
||||
secec.pt_scalar_mul_generator(&pub_key, &sc)
|
||||
secec.pt_rescale(&pub_key, &pub_key)
|
||||
priv_key._pub_key._impl = pub_key
|
||||
case X25519_Buf:
|
||||
pub_key: X25519_Buf = ---
|
||||
x25519.scalarmult_basepoint(pub_key[:], sc[:])
|
||||
priv_key._pub_key._impl = pub_key
|
||||
case X448_Buf:
|
||||
pub_key: X448_Buf = ---
|
||||
x448.scalarmult_basepoint(pub_key[:], sc[:])
|
||||
priv_key._pub_key._impl = pub_key
|
||||
case:
|
||||
panic("crypto/ecdh: invalid curve")
|
||||
}
|
||||
|
||||
priv_key._pub_key._curve = priv_key._curve
|
||||
}
|
||||
|
||||
// private_key_bytes sets dst to byte-encoding of priv_key.
|
||||
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
|
||||
ensure(priv_key._curve != .Invalid, "crypto/ecdh: uninitialized private key")
|
||||
ensure(len(dst) == PRIVATE_KEY_SIZES[priv_key._curve], "crypto/ecdh: invalid destination size")
|
||||
|
||||
#partial switch priv_key._curve {
|
||||
case .SECP256R1:
|
||||
sc := &priv_key._impl.(secec.Scalar_p256r1)
|
||||
secec.sc_bytes(dst, sc)
|
||||
case .X25519:
|
||||
sc := &priv_key._impl.(X25519_Buf)
|
||||
copy(dst, sc[:])
|
||||
case .X448:
|
||||
sc := &priv_key._impl.(X448_Buf)
|
||||
copy(dst, sc[:])
|
||||
case:
|
||||
panic("crypto/ecdh: invalid curve")
|
||||
}
|
||||
}
|
||||
|
||||
// private_key_equal returns true iff the private keys are equal,
|
||||
// in constant time.
|
||||
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
|
||||
if p._curve != q._curve {
|
||||
return false
|
||||
}
|
||||
|
||||
#partial switch p._curve {
|
||||
case .SECP256R1:
|
||||
sc_p, sc_q := &p._impl.(secec.Scalar_p256r1), &q._impl.(secec.Scalar_p256r1)
|
||||
return secec.sc_equal(sc_p, sc_q) == 1
|
||||
case .X25519:
|
||||
b_p, b_q := &p._impl.(X25519_Buf), &q._impl.(X25519_Buf)
|
||||
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
|
||||
case .X448:
|
||||
b_p, b_q := &p._impl.(X448_Buf), &q._impl.(X448_Buf)
|
||||
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
|
||||
case:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// public_key_set_bytes decodes a byte-encoded public key, and returns
|
||||
// true iff the operation was successful.
|
||||
public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> bool {
|
||||
public_key_clear(pub_key)
|
||||
|
||||
if len(b) != PUBLIC_KEY_SIZES[curve] {
|
||||
return false
|
||||
}
|
||||
|
||||
reflect.set_union_variant_typeid(
|
||||
pub_key._impl,
|
||||
_PUB_IMPL_IDS[curve],
|
||||
)
|
||||
|
||||
#partial switch curve {
|
||||
case .SECP256R1:
|
||||
if b[0] != secec.SEC_PREFIX_UNCOMPRESSED {
|
||||
return false
|
||||
}
|
||||
|
||||
pt := &pub_key._impl.(secec.Point_p256r1)
|
||||
ok := secec.pt_set_sec_bytes(pt, b)
|
||||
if !ok || secec.pt_is_identity(pt) == 1 {
|
||||
return false
|
||||
}
|
||||
case .X25519:
|
||||
pt := &pub_key._impl.(X25519_Buf)
|
||||
copy(pt[:], b)
|
||||
case .X448:
|
||||
pt := &pub_key._impl.(X448_Buf)
|
||||
copy(pt[:], b)
|
||||
case:
|
||||
panic("crypto/ecdh: invalid curve")
|
||||
}
|
||||
|
||||
pub_key._curve = curve
|
||||
|
||||
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) {
|
||||
ensure(priv_key._curve != .Invalid, "crypto/ecdh: uninitialized private key")
|
||||
public_key_clear(pub_key)
|
||||
pub_key^ = priv_key._pub_key
|
||||
}
|
||||
|
||||
// public_key_bytes sets dst to byte-encoding of pub_key.
|
||||
public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
|
||||
ensure(pub_key._curve != .Invalid, "crypto/ecdh: uninitialized public key")
|
||||
ensure(len(dst) == PUBLIC_KEY_SIZES[pub_key._curve], "crypto/ecdh: invalid destination size")
|
||||
|
||||
#partial switch pub_key._curve {
|
||||
case .SECP256R1:
|
||||
// Invariant: Unless the caller is manually building pub_key
|
||||
// `Z = 1`, so we can skip the rescale.
|
||||
pt := &pub_key._impl.(secec.Point_p256r1)
|
||||
|
||||
dst[0] = secec.SEC_PREFIX_UNCOMPRESSED
|
||||
secec.fe_bytes(dst[1:1+secec.FE_SIZE_P256R1], &pt.x)
|
||||
secec.fe_bytes(dst[1+secec.FE_SIZE_P256R1:], &pt.y)
|
||||
case .X25519:
|
||||
pt := &pub_key._impl.(X25519_Buf)
|
||||
copy(dst, pt[:])
|
||||
case .X448:
|
||||
pt := &pub_key._impl.(X448_Buf)
|
||||
copy(dst, pt[:])
|
||||
case:
|
||||
panic("crypto/ecdh: invalid curve")
|
||||
}
|
||||
}
|
||||
|
||||
// public_key_equal returns true iff the public keys are equal,
|
||||
// in constant time.
|
||||
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
|
||||
if p._curve != q._curve {
|
||||
return false
|
||||
}
|
||||
|
||||
#partial switch p._curve {
|
||||
case .SECP256R1:
|
||||
pt_p, pt_q := &p._impl.(secec.Point_p256r1), &q._impl.(secec.Point_p256r1)
|
||||
return secec.pt_equal(pt_p, pt_q) == 1
|
||||
case .X25519:
|
||||
b_p, b_q := &p._impl.(X25519_Buf), &q._impl.(X25519_Buf)
|
||||
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
|
||||
case .X448:
|
||||
b_p, b_q := &p._impl.(X448_Buf), &q._impl.(X448_Buf)
|
||||
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
|
||||
case:
|
||||
panic("crypto/ecdh: invalid curve")
|
||||
}
|
||||
}
|
||||
|
||||
// public_key_clear clears pub_key to the uninitialized state.
|
||||
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
|
||||
mem.zero_explicit(pub_key, size_of(Public_Key))
|
||||
}
|
||||
|
||||
// ecdh performs an Elliptic Curve Diffie-Hellman key exchange betwween
|
||||
// the Private_Key and Public_Key, writing the shared secret to dst.
|
||||
//
|
||||
// The neutral element is rejected as an error.
|
||||
@(require_results)
|
||||
ecdh :: proc(priv_key: ^Private_Key, pub_key: ^Public_Key, dst: []byte) -> bool {
|
||||
ensure(priv_key._curve == pub_key._curve, "crypto/ecdh: curve mismatch")
|
||||
ensure(pub_key._curve != .Invalid, "crypto/ecdh: uninitialized public key")
|
||||
ensure(len(dst) == SHARED_SECRET_SIZES[priv_key._curve], "crypto/ecdh: invalid shared secret size")
|
||||
|
||||
#partial switch priv_key._curve {
|
||||
case .SECP256R1:
|
||||
sc, pt := &priv_key._impl.(secec.Scalar_p256r1), &pub_key._impl.(secec.Point_p256r1)
|
||||
ss: secec.Point_p256r1
|
||||
defer secec.pt_clear(&ss)
|
||||
|
||||
secec.pt_scalar_mul(&ss, pt, sc)
|
||||
return secec.pt_bytes(dst, nil, &ss)
|
||||
case .X25519:
|
||||
sc, pt := &priv_key._impl.(X25519_Buf), &pub_key._impl.(X25519_Buf)
|
||||
x25519.scalarmult(dst, sc[:], pt[:])
|
||||
case .X448:
|
||||
sc, pt := &priv_key._impl.(X448_Buf), &pub_key._impl.(X448_Buf)
|
||||
x448.scalarmult(dst, sc[:], pt[:])
|
||||
case:
|
||||
panic("crypto/ecdh: invalid curve")
|
||||
}
|
||||
|
||||
// X25519/X448 check for all zero digest.
|
||||
return crypto.is_zero_constant_time(dst) == 0
|
||||
}
|
||||
|
||||
// curve returns the Curve used by a Private_Key or Public_Key instance.
|
||||
curve :: proc(k: ^$T) -> Curve where(T == Private_Key || T == Public_Key) {
|
||||
return k._curve
|
||||
}
|
||||
|
||||
// key_size returns the key size of a Private_Key or Public_Key in bytes.
|
||||
key_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
|
||||
when T == Private_Key {
|
||||
return PRIVATE_KEY_SIZES[k._curve]
|
||||
} else {
|
||||
return PUBLIC_KEY_SIZES[k._curve]
|
||||
}
|
||||
}
|
||||
|
||||
// shared_secret_size returns the shared secret size of a key exchange
|
||||
// in bytes.
|
||||
shared_secret_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
|
||||
return SHARED_SECRET_SIZES[k._curve]
|
||||
}
|
||||
@@ -33,6 +33,7 @@ package all
|
||||
@(require) import "core:crypto/chacha20poly1305"
|
||||
@(require) import chash "core:crypto/hash"
|
||||
@(require) import "core:crypto/deoxysii"
|
||||
@(require) import "core:crypto/ecdh"
|
||||
@(require) import "core:crypto/ed25519"
|
||||
@(require) import "core:crypto/hkdf"
|
||||
@(require) import "core:crypto/hmac"
|
||||
|
||||
@@ -37,6 +37,7 @@ package all
|
||||
@(require) import "core:crypto/chacha20poly1305"
|
||||
@(require) import chash "core:crypto/hash"
|
||||
@(require) import "core:crypto/deoxysii"
|
||||
@(require) import "core:crypto/ecdh"
|
||||
@(require) import "core:crypto/ed25519"
|
||||
@(require) import "core:crypto/hkdf"
|
||||
@(require) import "core:crypto/hmac"
|
||||
|
||||
@@ -2,13 +2,14 @@ package benchmark_core_crypto
|
||||
|
||||
import "base:runtime"
|
||||
import "core:encoding/hex"
|
||||
import "core:log"
|
||||
import "core:testing"
|
||||
import "core:text/table"
|
||||
import "core:time"
|
||||
|
||||
import "core:crypto"
|
||||
import "core:crypto/ecdh"
|
||||
import "core:crypto/ed25519"
|
||||
import "core:crypto/x25519"
|
||||
import "core:crypto/x448"
|
||||
|
||||
@(private = "file")
|
||||
ECDH_ITERS :: 10000
|
||||
@@ -25,6 +26,10 @@ benchmark_crypto_ecc :: proc(t: ^testing.T) {
|
||||
|
||||
@(private = "file")
|
||||
bench_ecdh :: proc() {
|
||||
if !crypto.HAS_RAND_BYTES {
|
||||
log.warnf("ECDH benchmarks skipped, no system entropy source")
|
||||
}
|
||||
|
||||
tbl: table.Table
|
||||
table.init(&tbl)
|
||||
defer table.destroy(&tbl)
|
||||
@@ -42,63 +47,37 @@ bench_ecdh :: proc() {
|
||||
)
|
||||
}
|
||||
|
||||
scalar_bp, scalar := bench_x25519()
|
||||
append_tbl(&tbl, "X25519", scalar_bp, scalar)
|
||||
for algo in ecdh.Curve {
|
||||
if algo == .Invalid {
|
||||
continue
|
||||
}
|
||||
algo_name := ecdh.CURVE_NAMES[algo]
|
||||
|
||||
scalar_bp, scalar = bench_x448()
|
||||
append_tbl(&tbl, "X448", scalar_bp, scalar)
|
||||
priv_key_alice: ecdh.Private_Key
|
||||
start := time.tick_now()
|
||||
for _ in 0 ..< ECDH_ITERS {
|
||||
_ = ecdh.private_key_generate(&priv_key_alice, algo)
|
||||
}
|
||||
bp := time.tick_since(start) / ECDH_ITERS
|
||||
|
||||
pub_key_alice: ecdh.Public_Key
|
||||
ecdh.public_key_set_priv(&pub_key_alice, &priv_key_alice)
|
||||
|
||||
priv_key_bob: ecdh.Private_Key
|
||||
_ = ecdh.private_key_generate(&priv_key_bob, algo)
|
||||
ss := make([]byte, ecdh.SHARED_SECRET_SIZES[algo], context.temp_allocator)
|
||||
start = time.tick_now()
|
||||
for _ in 0 ..< ECDH_ITERS {
|
||||
_ = ecdh.ecdh(&priv_key_bob, &pub_key_alice, ss)
|
||||
}
|
||||
sc := time.tick_since(start) / ECDH_ITERS
|
||||
|
||||
append_tbl(&tbl, algo_name, bp, sc)
|
||||
}
|
||||
|
||||
log_table(&tbl)
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
bench_x25519 :: proc() -> (bp, sc: time.Duration) {
|
||||
point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
|
||||
|
||||
point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator)
|
||||
scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator)
|
||||
out: [x25519.POINT_SIZE]byte = ---
|
||||
|
||||
start := time.tick_now()
|
||||
for _ in 0 ..< ECDH_ITERS {
|
||||
x25519.scalarmult_basepoint(out[:], scalar[:])
|
||||
}
|
||||
bp = time.tick_since(start) / ECDH_ITERS
|
||||
|
||||
start = time.tick_now()
|
||||
for _ in 0 ..< ECDH_ITERS {
|
||||
x25519.scalarmult(out[:], scalar[:], point[:])
|
||||
}
|
||||
sc = time.tick_since(start) / ECDH_ITERS
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
bench_x448 :: proc() -> (bp, sc: time.Duration) {
|
||||
point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
|
||||
|
||||
point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator)
|
||||
scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator)
|
||||
out: [x448.POINT_SIZE]byte = ---
|
||||
|
||||
start := time.tick_now()
|
||||
for _ in 0 ..< ECDH_ITERS {
|
||||
x448.scalarmult_basepoint(out[:], scalar[:])
|
||||
}
|
||||
bp = time.tick_since(start) / ECDH_ITERS
|
||||
|
||||
start = time.tick_now()
|
||||
for _ in 0 ..< ECDH_ITERS {
|
||||
x448.scalarmult(out[:], scalar[:], point[:])
|
||||
}
|
||||
sc = time.tick_since(start) / ECDH_ITERS
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
bench_dsa :: proc() {
|
||||
tbl: table.Table
|
||||
|
||||
154
tests/core/crypto/test_core_crypto_ecdh.odin
Normal file
154
tests/core/crypto/test_core_crypto_ecdh.odin
Normal file
@@ -0,0 +1,154 @@
|
||||
package test_core_crypto
|
||||
|
||||
import "core:encoding/hex"
|
||||
import "core:testing"
|
||||
|
||||
import "core:crypto/ecdh"
|
||||
|
||||
@(test)
|
||||
test_ecdh :: proc(t: ^testing.T) {
|
||||
test_vectors := []struct {
|
||||
curve: ecdh.Curve,
|
||||
scalar: string,
|
||||
point: string,
|
||||
product: string,
|
||||
} {
|
||||
// X25519 Test vectors from RFC 7748
|
||||
{
|
||||
.X25519,
|
||||
"a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4",
|
||||
"e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c",
|
||||
"c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552",
|
||||
},
|
||||
{
|
||||
.X25519,
|
||||
"4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d",
|
||||
"e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493",
|
||||
"95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957",
|
||||
},
|
||||
// X448 Test vectors from RFC 7748
|
||||
{
|
||||
.X448,
|
||||
"3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3",
|
||||
"06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086",
|
||||
"ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f",
|
||||
},
|
||||
{
|
||||
.X448,
|
||||
"203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f",
|
||||
"0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db",
|
||||
"884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d",
|
||||
},
|
||||
// secp256r1 Test vectors (subset) from NIST CAVP
|
||||
{
|
||||
.SECP256R1,
|
||||
"7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534",
|
||||
"04700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac",
|
||||
"46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b",
|
||||
},
|
||||
{
|
||||
.SECP256R1,
|
||||
"38f65d6dce47676044d58ce5139582d568f64bb16098d179dbab07741dd5caf5",
|
||||
"04809f04289c64348c01515eb03d5ce7ac1a8cb9498f5caa50197e58d43a86a7aeb29d84e811197f25eba8f5194092cb6ff440e26d4421011372461f579271cda3",
|
||||
"057d636096cb80b67a8c038c890e887d1adfa4195e9b3ce241c8a778c59cda67",
|
||||
},
|
||||
}
|
||||
|
||||
for v, _ in test_vectors {
|
||||
raw_scalar, _ := hex.decode(transmute([]byte)(v.scalar), context.temp_allocator)
|
||||
raw_point, _ := hex.decode(transmute([]byte)(v.point), context.temp_allocator)
|
||||
|
||||
pub_key: ecdh.Public_Key
|
||||
priv_key: ecdh.Private_Key
|
||||
|
||||
ok := ecdh.private_key_set_bytes(&priv_key, v.curve, raw_scalar)
|
||||
testing.expectf(t, ok, "failed to deserialize private key: %v %x", v.curve, raw_scalar)
|
||||
|
||||
ok = ecdh.public_key_set_bytes(&pub_key, v.curve, raw_point)
|
||||
testing.expectf(t, ok, "failed to deserialize public key: %v %x", v.curve, raw_scalar)
|
||||
|
||||
shared_secret := make([]byte, ecdh.shared_secret_size(&pub_key), context.temp_allocator)
|
||||
ok = ecdh.ecdh(&priv_key, &pub_key, shared_secret)
|
||||
testing.expectf(t, ok, "ecdh failed: %v %v %v", v.curve, &priv_key, &pub_key)
|
||||
|
||||
ss_str := string(hex.encode(shared_secret, context.temp_allocator))
|
||||
testing.expectf(
|
||||
t,
|
||||
ss_str == v.product,
|
||||
"Expected %s for %v %s * %s, but got %s instead",
|
||||
v.product,
|
||||
v.curve,
|
||||
v.scalar,
|
||||
v.point,
|
||||
ss_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_ecdh_scalar_basemult :: proc(t: ^testing.T) {
|
||||
test_vectors := []struct {
|
||||
curve: ecdh.Curve,
|
||||
scalar : string,
|
||||
point: string,
|
||||
} {
|
||||
// X25519 from RFC 7748 6.1
|
||||
{
|
||||
.X25519,
|
||||
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
|
||||
"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
|
||||
},
|
||||
{
|
||||
.X25519,
|
||||
"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb",
|
||||
"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
|
||||
},
|
||||
// X448 from RFC 7748 6.2
|
||||
{
|
||||
.X448,
|
||||
"9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b",
|
||||
"9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0",
|
||||
},
|
||||
{
|
||||
.X448,
|
||||
"1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d",
|
||||
"3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609",
|
||||
},
|
||||
// secp256r1 Test vectors (subset) from NIST CAVP
|
||||
{
|
||||
.SECP256R1,
|
||||
"7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534",
|
||||
"04ead218590119e8876b29146ff89ca61770c4edbbf97d38ce385ed281d8a6b23028af61281fd35e2fa7002523acc85a429cb06ee6648325389f59edfce1405141",
|
||||
},
|
||||
{
|
||||
.SECP256R1,
|
||||
"38f65d6dce47676044d58ce5139582d568f64bb16098d179dbab07741dd5caf5",
|
||||
"04119f2f047902782ab0c9e27a54aff5eb9b964829ca99c06b02ddba95b0a3f6d08f52b726664cac366fc98ac7a012b2682cbd962e5acb544671d41b9445704d1d",
|
||||
},
|
||||
}
|
||||
|
||||
for v, _ in test_vectors {
|
||||
raw_scalar, _ := hex.decode(transmute([]byte)(v.scalar), context.temp_allocator)
|
||||
|
||||
priv_key: ecdh.Private_Key
|
||||
pub_key: ecdh.Public_Key
|
||||
|
||||
ok := ecdh.private_key_set_bytes(&priv_key, v.curve, raw_scalar)
|
||||
testing.expectf(t, ok, "failed to deserialize private key: %v %x", v.curve, raw_scalar)
|
||||
|
||||
ecdh.public_key_set_priv(&pub_key, &priv_key)
|
||||
b := make([]byte, ecdh.key_size(&pub_key), context.temp_allocator)
|
||||
ecdh.public_key_bytes(&pub_key, b)
|
||||
|
||||
pub_str := string(hex.encode(b, context.temp_allocator))
|
||||
testing.expectf(
|
||||
t,
|
||||
pub_str == v.point,
|
||||
"Expected %s for %v %s * G, but got %s instead",
|
||||
v.point,
|
||||
v.curve,
|
||||
v.scalar,
|
||||
pub_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import "core:testing"
|
||||
import field "core:crypto/_fiat/field_curve25519"
|
||||
import "core:crypto/ed25519"
|
||||
import "core:crypto/ristretto255"
|
||||
import "core:crypto/x25519"
|
||||
import "core:crypto/x448"
|
||||
|
||||
@(test)
|
||||
test_edwards25519_sqrt_ratio_m1 :: proc(t: ^testing.T) {
|
||||
@@ -625,128 +623,6 @@ test_ed25519 :: proc(t: ^testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_x25519 :: proc(t: ^testing.T) {
|
||||
// Local copy of this so that the base point doesn't need to be exported.
|
||||
_BASE_POINT: [32]byte = {
|
||||
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
test_vectors := []struct {
|
||||
scalar: string,
|
||||
point: string,
|
||||
product: string,
|
||||
} {
|
||||
// Test vectors from RFC 7748
|
||||
{
|
||||
"a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4",
|
||||
"e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c",
|
||||
"c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552",
|
||||
},
|
||||
{
|
||||
"4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d",
|
||||
"e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493",
|
||||
"95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957",
|
||||
},
|
||||
}
|
||||
for v, _ in test_vectors {
|
||||
scalar, _ := hex.decode(transmute([]byte)(v.scalar), context.temp_allocator)
|
||||
point, _ := hex.decode(transmute([]byte)(v.point), context.temp_allocator)
|
||||
|
||||
derived_point: [x25519.POINT_SIZE]byte
|
||||
x25519.scalarmult(derived_point[:], scalar[:], point[:])
|
||||
derived_point_str := string(hex.encode(derived_point[:], context.temp_allocator))
|
||||
|
||||
testing.expectf(
|
||||
t,
|
||||
derived_point_str == v.product,
|
||||
"Expected %s for %s * %s, but got %s instead",
|
||||
v.product,
|
||||
v.scalar,
|
||||
v.point,
|
||||
derived_point_str,
|
||||
)
|
||||
|
||||
// Abuse the test vectors to sanity-check the scalar-basepoint multiply.
|
||||
p1, p2: [x25519.POINT_SIZE]byte
|
||||
x25519.scalarmult_basepoint(p1[:], scalar[:])
|
||||
x25519.scalarmult(p2[:], scalar[:], _BASE_POINT[:])
|
||||
p1_str := string(hex.encode(p1[:], context.temp_allocator))
|
||||
p2_str := string(hex.encode(p2[:], context.temp_allocator))
|
||||
testing.expectf(
|
||||
t,
|
||||
p1_str == p2_str,
|
||||
"Expected %s for %s * basepoint, but got %s instead",
|
||||
p2_str,
|
||||
v.scalar,
|
||||
p1_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_x448 :: proc(t: ^testing.T) {
|
||||
// Local copy of this so that the base point doesn't need to be exported.
|
||||
_BASE_POINT: [56]byte = {
|
||||
5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
test_vectors := []struct {
|
||||
scalar: string,
|
||||
point: string,
|
||||
product: string,
|
||||
} {
|
||||
// Test vectors from RFC 7748
|
||||
{
|
||||
"3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3",
|
||||
"06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086",
|
||||
"ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f",
|
||||
},
|
||||
{
|
||||
"203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f",
|
||||
"0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db",
|
||||
"884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d",
|
||||
},
|
||||
}
|
||||
for v, _ in test_vectors {
|
||||
scalar, _ := hex.decode(transmute([]byte)(v.scalar), context.temp_allocator)
|
||||
point, _ := hex.decode(transmute([]byte)(v.point), context.temp_allocator)
|
||||
|
||||
derived_point: [x448.POINT_SIZE]byte
|
||||
x448.scalarmult(derived_point[:], scalar[:], point[:])
|
||||
derived_point_str := string(hex.encode(derived_point[:], context.temp_allocator))
|
||||
|
||||
testing.expectf(
|
||||
t,
|
||||
derived_point_str == v.product,
|
||||
"Expected %s for %s * %s, but got %s instead",
|
||||
v.product,
|
||||
v.scalar,
|
||||
v.point,
|
||||
derived_point_str,
|
||||
)
|
||||
|
||||
// Abuse the test vectors to sanity-check the scalar-basepoint multiply.
|
||||
p1, p2: [x448.POINT_SIZE]byte
|
||||
x448.scalarmult_basepoint(p1[:], scalar[:])
|
||||
x448.scalarmult(p2[:], scalar[:], _BASE_POINT[:])
|
||||
p1_str := string(hex.encode(p1[:], context.temp_allocator))
|
||||
p2_str := string(hex.encode(p2[:], context.temp_allocator))
|
||||
testing.expectf(
|
||||
t,
|
||||
p1_str == p2_str,
|
||||
"Expected %s for %s * basepoint, but got %s instead",
|
||||
p2_str,
|
||||
v.scalar,
|
||||
p1_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
ge_str :: proc(ge: ^ristretto255.Group_Element) -> string {
|
||||
b: [ristretto255.ELEMENT_SIZE]byte
|
||||
|
||||
Reference in New Issue
Block a user