Skip to content

Commit 0c6ee30

Browse files
committed
core/crypto/ecdh: Initial import
1 parent d3b4541 commit 0c6ee30

File tree

6 files changed

+429
-124
lines changed

6 files changed

+429
-124
lines changed

core/crypto/ecdh/doc.odin

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/*
2+
A generic interface to Elliptic Curve Diffie-Hellman key exchange.
3+
*/
4+
package ecdh

core/crypto/ecdh/ecdh.odin

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
package ecdh
2+
3+
import "core:crypto"
4+
import secec "core:crypto/_weierstrass"
5+
import "core:crypto/x25519"
6+
import "core:crypto/x448"
7+
import "core:mem"
8+
import "core:reflect"
9+
10+
// Note: For these primitives scalar size = point size
11+
@(private="file")
12+
X25519_Buf :: [x25519.SCALAR_SIZE]byte
13+
@(private="file")
14+
X448_Buf :: [x448.SCALAR_SIZE]byte
15+
16+
// Curve the curve identifier associated with a given Private_Key
17+
// or Public_Key
18+
Curve :: enum {
19+
Invalid,
20+
SECP256R1,
21+
X25519,
22+
X448,
23+
}
24+
25+
// CURVE_NAMES is the Curve to curve name string.
26+
CURVE_NAMES := [Curve]string {
27+
.Invalid = "Invalid",
28+
.SECP256R1 = "secp256r1",
29+
.X25519 = "X25519",
30+
.X448 = "X448",
31+
}
32+
33+
// PRIVATE_KEY_SIZES is the Curve to private key size in bytes.
34+
PRIVATE_KEY_SIZES := [Curve]int {
35+
.Invalid = 0,
36+
.SECP256R1 = secec.SC_SIZE_P256R1,
37+
.X25519 = x25519.SCALAR_SIZE,
38+
.X448 = x448.SCALAR_SIZE,
39+
}
40+
41+
// PUBLIC_KEY_SIZES is the Curve to public key size in bytes.
42+
PUBLIC_KEY_SIZES := [Curve]int {
43+
.Invalid = 0,
44+
.SECP256R1 = 1 + 2 * secec.FE_SIZE_P256R1,
45+
.X25519 = x25519.POINT_SIZE,
46+
.X448 = x448.POINT_SIZE,
47+
}
48+
49+
// SHARED_SECRET_SIZES is the Curve to shared secret size in bytes.
50+
SHARED_SECRET_SIZES := [Curve]int {
51+
.Invalid = 0,
52+
.SECP256R1 = secec.FE_SIZE_P256R1,
53+
.X25519 = x25519.POINT_SIZE,
54+
.X448 = x448.POINT_SIZE,
55+
}
56+
57+
@(private="file")
58+
_PRIV_IMPL_IDS := [Curve]typeid {
59+
.Invalid = nil,
60+
.SECP256R1 = typeid_of(secec.Scalar_p256r1),
61+
.X25519 = typeid_of(X25519_Buf),
62+
.X448 = typeid_of(X448_Buf),
63+
}
64+
65+
@(private="file")
66+
_PUB_IMPL_IDS := [Curve]typeid {
67+
.Invalid = nil,
68+
.SECP256R1 = typeid_of(secec.Point_p256r1),
69+
.X25519 = typeid_of(X25519_Buf),
70+
.X448 = typeid_of(X448_Buf),
71+
}
72+
73+
// Private_Key is an ECDH private key.
74+
Private_Key :: struct {
75+
// WARNING: All of the members are to be treated as internal (ie:
76+
// the Private_Key structure is intended to be opaque).
77+
_curve: Curve,
78+
_impl: union {
79+
secec.Scalar_p256r1,
80+
X25519_Buf,
81+
X448_Buf,
82+
},
83+
_pub_key: Public_Key,
84+
}
85+
86+
// Public_Key is an ECDH public key.
87+
Public_Key :: struct {
88+
// WARNING: All of the members are to be treated as internal (ie:
89+
// the Public_Key structure is intended to be opaque).
90+
_curve: Curve,
91+
_impl: union {
92+
secec.Point_p256r1,
93+
X25519_Buf,
94+
X448_Buf,
95+
},
96+
}
97+
98+
// private_key_set_bytes decodes a byte-encoded private key, and returns
99+
// true iff the operation was successful.
100+
private_key_set_bytes :: proc(priv_key: ^Private_Key, curve: Curve, b: []byte) -> bool {
101+
private_key_clear(priv_key)
102+
103+
if len(b) != PRIVATE_KEY_SIZES[curve] {
104+
return false
105+
}
106+
107+
reflect.set_union_variant_typeid(
108+
priv_key._impl,
109+
_PRIV_IMPL_IDS[curve],
110+
)
111+
112+
#partial switch curve {
113+
case .SECP256R1:
114+
sc := &priv_key._impl.(secec.Scalar_p256r1)
115+
did_reduce := secec.sc_set_bytes(sc, b)
116+
is_zero := secec.sc_is_zero(sc) == 1
117+
118+
// Reject `0` and scalars that are not less than the
119+
// curve order.
120+
if did_reduce || is_zero {
121+
private_key_clear(priv_key)
122+
return false
123+
}
124+
125+
pub_key: secec.Point_p256r1
126+
secec.pt_scalar_mul_generator(&pub_key, sc)
127+
secec.pt_rescale(&pub_key, &pub_key)
128+
priv_key._pub_key._curve = curve
129+
priv_key._pub_key._impl = pub_key
130+
case .X25519:
131+
sc := &priv_key._impl.(X25519_Buf)
132+
copy(sc[:], b)
133+
134+
pub_key: X25519_Buf = ---
135+
x25519.scalarmult_basepoint(pub_key[:], sc[:])
136+
priv_key._pub_key._curve = curve
137+
priv_key._pub_key._impl = pub_key
138+
case .X448:
139+
sc := &priv_key._impl.(X448_Buf)
140+
copy(sc[:], b)
141+
142+
pub_key: X448_Buf = ---
143+
x448.scalarmult_basepoint(pub_key[:], sc[:])
144+
priv_key._pub_key._curve = curve
145+
priv_key._pub_key._impl = pub_key
146+
case:
147+
panic("crypto/ecdh: invalid curve")
148+
}
149+
150+
priv_key._curve = curve
151+
152+
return true
153+
}
154+
155+
// private_key_bytes sets dst to byte-encoding of priv_key.
156+
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
157+
ensure(priv_key._curve != .Invalid, "crypto/ecdh: uninitialized private key")
158+
ensure(len(dst) == PRIVATE_KEY_SIZES[priv_key._curve], "crypto/ecdh: invalid destination size")
159+
160+
#partial switch priv_key._curve {
161+
case .SECP256R1:
162+
sc := &priv_key._impl.(secec.Scalar_p256r1)
163+
secec.sc_bytes(dst, sc)
164+
case .X25519:
165+
sc := &priv_key._impl.(X25519_Buf)
166+
copy(dst, sc[:])
167+
case .X448:
168+
sc := &priv_key._impl.(X448_Buf)
169+
copy(dst, sc[:])
170+
case:
171+
panic("crypto/ecdh: invalid curve")
172+
}
173+
}
174+
175+
// private_key_equal returns true iff the private keys are equal,
176+
// in constant time.
177+
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
178+
if p._curve != q._curve {
179+
return false
180+
}
181+
182+
#partial switch p._curve {
183+
case .SECP256R1:
184+
sc_p, sc_q := &p._impl.(secec.Scalar_p256r1), &q._impl.(secec.Scalar_p256r1)
185+
return secec.sc_equal(sc_p, sc_q) == 1
186+
case .X25519:
187+
b_p, b_q := &p._impl.(X25519_Buf), &q._impl.(X25519_Buf)
188+
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
189+
case .X448:
190+
b_p, b_q := &p._impl.(X448_Buf), &q._impl.(X448_Buf)
191+
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
192+
case:
193+
return false
194+
}
195+
}
196+
197+
// private_key_clear clears priv_key to the uninitialized state.
198+
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
199+
mem.zero_explicit(priv_key, size_of(Private_Key))
200+
}
201+
202+
// public_key_set_bytes decodes a byte-encoded public key, and returns
203+
// true iff the operation was successful.
204+
public_key_set_bytes :: proc(pub_key: ^Public_Key, curve: Curve, b: []byte) -> bool {
205+
public_key_clear(pub_key)
206+
207+
if len(b) != PUBLIC_KEY_SIZES[curve] {
208+
return false
209+
}
210+
211+
reflect.set_union_variant_typeid(
212+
pub_key._impl,
213+
_PUB_IMPL_IDS[curve],
214+
)
215+
216+
#partial switch curve {
217+
case .SECP256R1:
218+
if b[0] != secec.SEC_PREFIX_UNCOMPRESSED {
219+
return false
220+
}
221+
222+
pt := &pub_key._impl.(secec.Point_p256r1)
223+
ok := secec.pt_set_sec_bytes(pt, b)
224+
if !ok || secec.pt_is_identity(pt) == 1 {
225+
return false
226+
}
227+
case .X25519:
228+
pt := &pub_key._impl.(X25519_Buf)
229+
copy(pt[:], b)
230+
case .X448:
231+
pt := &pub_key._impl.(X448_Buf)
232+
copy(pt[:], b)
233+
case:
234+
panic("crypto/ecdh: invalid curve")
235+
}
236+
237+
pub_key._curve = curve
238+
239+
return true
240+
}
241+
242+
// public_key_set_priv sets pub_key to the public component of priv_key.
243+
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
244+
ensure(priv_key._curve != .Invalid, "crypto/ecdh: uninitialized private key")
245+
public_key_clear(pub_key)
246+
pub_key^ = priv_key._pub_key
247+
}
248+
249+
// public_key_bytes sets dst to byte-encoding of pub_key.
250+
public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
251+
ensure(pub_key._curve != .Invalid, "crypto/ecdh: uninitialized public key")
252+
ensure(len(dst) == PUBLIC_KEY_SIZES[pub_key._curve], "crypto/ecdh: invalid destination size")
253+
254+
#partial switch pub_key._curve {
255+
case .SECP256R1:
256+
// Invariant: Unless the caller is manually building pub_key
257+
// `Z = 1`, so we can skip the rescale.
258+
pt := &pub_key._impl.(secec.Point_p256r1)
259+
260+
dst[0] = secec.SEC_PREFIX_UNCOMPRESSED
261+
secec.fe_bytes(dst[1:1+secec.FE_SIZE_P256R1], &pt._x)
262+
secec.fe_bytes(dst[1+secec.FE_SIZE_P256R1:], &pt._y)
263+
case .X25519:
264+
pt := &pub_key._impl.(X25519_Buf)
265+
copy(dst, pt[:])
266+
case .X448:
267+
pt := &pub_key._impl.(X448_Buf)
268+
copy(dst, pt[:])
269+
case:
270+
panic("crypto/ecdh: invalid curve")
271+
}
272+
}
273+
274+
// public_key_equal returns true iff the public keys are equal,
275+
// in constant time.
276+
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
277+
if p._curve != q._curve {
278+
return false
279+
}
280+
281+
#partial switch p._curve {
282+
case .SECP256R1:
283+
pt_p, pt_q := &p._impl.(secec.Point_p256r1), &q._impl.(secec.Point_p256r1)
284+
return secec.pt_equal(pt_p, pt_q) == 1
285+
case .X25519:
286+
b_p, b_q := &p._impl.(X25519_Buf), &q._impl.(X25519_Buf)
287+
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
288+
case .X448:
289+
b_p, b_q := &p._impl.(X448_Buf), &q._impl.(X448_Buf)
290+
return crypto.compare_constant_time(b_p[:], b_q[:]) == 1
291+
case:
292+
panic("crypto/ecdh: invalid curve")
293+
}
294+
}
295+
296+
// public_key_clear clears pub_key to the uninitialized state.
297+
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
298+
mem.zero_explicit(pub_key, size_of(Public_Key))
299+
}
300+
301+
// ecdh performs an Elliptic Curve Diffie-Hellman key exchange betwween
302+
// the Private_Key and Public_Key, writing the shared secret to dst.
303+
//
304+
// The neutral element is rejected as an error.
305+
@(require_results)
306+
ecdh :: proc(priv_key: ^Private_Key, pub_key: ^Public_Key, dst: []byte) -> bool {
307+
ensure(priv_key._curve == pub_key._curve, "crypto/ecdh: curve mismatch")
308+
ensure(pub_key._curve != .Invalid, "crypto/ecdh: uninitialized public key")
309+
ensure(len(dst) == SHARED_SECRET_SIZES[priv_key._curve], "crypto/ecdh: invalid shared secret size")
310+
311+
#partial switch priv_key._curve {
312+
case .SECP256R1:
313+
sc, pt := &priv_key._impl.(secec.Scalar_p256r1), &pub_key._impl.(secec.Point_p256r1)
314+
ss: secec.Point_p256r1
315+
defer secec.pt_clear(&ss)
316+
317+
secec.pt_scalar_mul(&ss, pt, sc)
318+
return secec.pt_bytes(dst, nil, &ss)
319+
case .X25519:
320+
sc, pt := &priv_key._impl.(X25519_Buf), &pub_key._impl.(X25519_Buf)
321+
x25519.scalarmult(dst, sc[:], pt[:])
322+
case .X448:
323+
sc, pt := &priv_key._impl.(X448_Buf), &pub_key._impl.(X448_Buf)
324+
x448.scalarmult(dst, sc[:], pt[:])
325+
case:
326+
panic("crypto/ecdh: invalid curve")
327+
}
328+
329+
// X25519/X448 check for all zero digest.
330+
return crypto.is_zero_constant_time(dst) == 0
331+
}
332+
333+
// curve returns the Curve used by a Private_Key or Public_Key instance.
334+
curve :: proc(k: ^$T) -> Curve where(T == Private_Key || T == Public_Key) {
335+
return k._curve
336+
}
337+
338+
// key_size returns the key size of a Private_Key or Public_Key in bytes.
339+
key_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
340+
when T == Private_Key {
341+
return PRIVATE_KEY_SIZES[k._curve]
342+
} else {
343+
return PUBLIC_KEY_SIZES[k._curve]
344+
}
345+
}
346+
347+
// shared_secret_size returns the shared secret size of a key exchange
348+
// in bytes.
349+
shared_secret_size :: proc(k: ^$T) -> int where(T == Private_Key || T == Public_Key) {
350+
return SHARED_SECRET_SIZES[k._curve]
351+
}

examples/all/all_js.odin

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ package all
3333
@(require) import "core:crypto/chacha20poly1305"
3434
@(require) import chash "core:crypto/hash"
3535
@(require) import "core:crypto/deoxysii"
36+
@(require) import "core:crypto/ecdh"
3637
@(require) import "core:crypto/ed25519"
3738
@(require) import "core:crypto/hkdf"
3839
@(require) import "core:crypto/hmac"

examples/all/all_main.odin

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ package all
3636
@(require) import "core:crypto/chacha20poly1305"
3737
@(require) import chash "core:crypto/hash"
3838
@(require) import "core:crypto/deoxysii"
39+
@(require) import "core:crypto/ecdh"
3940
@(require) import "core:crypto/ed25519"
4041
@(require) import "core:crypto/hkdf"
4142
@(require) import "core:crypto/hmac"

0 commit comments

Comments
 (0)