v/vlib/x/crypto/curve25519/curve25519.v

412 lines
12 KiB
V

// Copyright © 2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// This module implements building block for elliptic-curve diffie-helman
// key exchange (ECDH) mechanism through curve25519 curve.
module curve25519
import crypto.rand
import crypto.internal.subtle
import crypto.ed25519.internal.edwards25519
// scalar_size is the size of the Curve25519 key
const scalar_size = 32
// point_size is the size of the Curve25519 point
const point_size = 32
// zero_point is point with 32 bytes length of zeros bytes
const zero_point = []u8{len: 32, init: u8(0x00)}
// base_point is the canonical Curve25519 generator, encoded as a byte with value 9,
// followed by 31 zero bytes
const base_point = [u8(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]
// PrivateKey represents Curve25519 private key
@[noinit]
pub struct PrivateKey {
mut:
// boolean flag that tells this key should not be
// used again when its true. its set after .free call
done bool
// clamped key, 32 bytes length
key []u8
}
// new creates a new Curve25519 key using randomly generated bytes from `crypto.rand`.
@[direct_array_access]
pub fn PrivateKey.new() !&PrivateKey {
mut bytes := rand.read(scalar_size)!
// do bytes clamping
clamp(mut bytes)!
if is_zero_point(bytes) || is_base_point(bytes) {
return error('PrivateKey.new: bytes zeros or base point')
}
return &PrivateKey{
key: bytes
}
}
// new_from_seed creates a new Curve25519 key from provided seed bytes.
@[direct_array_access]
pub fn PrivateKey.new_from_seed(seed []u8) !&PrivateKey {
if seed.len != scalar_size {
return error('invalid scalar size')
}
mut bytes := seed.clone()
clamp(mut bytes)!
if is_zero_point(bytes) || is_base_point(bytes) {
return error('PrivateKey.new_from_seed: bytes was zeros or base point')
}
return &PrivateKey{
key: bytes
}
}
// public_key returns the associated public key part of the PrivateKey.
@[direct_array_access]
pub fn (mut pv PrivateKey) public_key() !&PublicKey {
if pv.done {
return error('your key has been marked as freed')
}
out := x25519(mut pv.key, base_point)!
return &PublicKey{
key: out
}
}
// equal returns whether two private keys are equal.
@[direct_array_access]
pub fn (pv PrivateKey) equal(oth PrivateKey) bool {
if pv.done || oth.done {
panic('the key has been marked as freed')
}
if pv.key.len != scalar_size || oth.key.len != scalar_size {
return false
}
return subtle.constant_time_compare(pv.key, oth.key) == 1
}
// x25519 performs scalar multiplication between key and point and return another bytes (point).
@[direct_array_access]
pub fn (mut pv PrivateKey) x25519(point []u8) ![]u8 {
if pv.done {
return error('PrivateKey has been marked as freed')
}
if point.len != point_size {
return error('bad point size, should be 32')
}
// We reject and disallow zero-bytes point to be passed
// and check it here as a quick exit before heavy math
// calculation on `x25519` call
if is_zero(point) {
return error('x25519: get zeros point')
}
// even technically its possible, but we limit to unallow it
if subtle.constant_time_compare(pv.key, point) == 1 {
return error('pv.key identical with point')
}
out := x25519(mut pv.key, point)!
return out
}
// bytes return a clone of the bytes of the underlying PrivateKey
pub fn (pv PrivateKey) bytes() ![]u8 {
if pv.done {
return error('PrivateKey has been marked as freed')
}
return pv.key.clone()
}
// free releases underlying key. Dont use the key after calling .free
@[unsafe]
pub fn (mut pv PrivateKey) free() {
// when private key has been marked as done (freed),
// calling free on already freed key would lead to undefined behavior.
// so, we check it
if pv.done {
return
}
unsafe { pv.key.free() }
// sets flag to finish
pv.done = true
}
// PublicKey represent Curve25519 key.
@[noinit]
pub struct PublicKey {
mut:
// 32 bytes length of scalar * point
key []u8
}
// new_from_bytes creates a new Curve25519 public key from provided bytes.
pub fn PublicKey.new_from_bytes(bytes []u8) !&PublicKey {
if bytes.len != point_size {
return error('PublicKey.new: bad bytes length')
}
// Refers to the D.J. Bernstein, the designer of the curve25519, public key validation
// in curve25519 is generally not needed for Diffie-Hellman key exchange.
// See https://cr.yp.to/ecdh.html#validate
// But there are availables suggestion to do validation on them spreads on the internet, likes
// - blacklisting the known bad public keys
// - check the shared value and to raise exception if it is zero.
// - You can also bind the exchanged public keys to the shared keys, i.e.,
// instead of using H(abG) as the shared keys, you should use H(aG || bG || abG)
//
// We only, check for zeros public key
if is_zero(bytes) {
return error('PublicKey.new: get zeros bytes')
}
// otherwise, we can return it
return &PublicKey{
key: bytes
}
}
// equal tells whether two public keys are equal
pub fn (pb PublicKey) equal(other PublicKey) bool {
// different length, should not happen
if pb.key.len != point_size || other.key.len != point_size {
return false
}
return subtle.constant_time_compare(pb.key, other.key) == 1
}
// bytes return the clone of the bytes of the underlying PublicKey
pub fn (pb PublicKey) bytes() ![]u8 {
if pb.key.len != point_size {
return error('bad public key size')
}
return pb.key.clone()
}
// SharedOpts is the configuration options to `derive_shared_secret` routine
@[params]
pub struct SharedOpts {
pub mut:
should_derive bool
derivator Derivator = RawDerivator{}
drv_opts DeriveOpts
}
// DeriveOpts is config to drive the Derivator's derive operation.
@[params]
pub struct DeriveOpts {}
// Derivator represent key derivation function
pub interface Derivator {
// derive transforms bytes in sec into another form of bytes.
derive(sec []u8, opt DeriveOpts) ![]u8
}
// RawDerivator was a simple derivator with no derivation behaviour.
struct RawDerivator {}
fn (rd RawDerivator) derive(sec []u8, opt DeriveOpts) ![]u8 {
return sec
}
// derive_shared_secret derives a shared secret between two peer's
// between first private key's peer and the second PublicKey's peer.
// Its accepts SharedOpts options to advance supports for other key derivation mechanism.
//
// 6. Diffie-Hellman with Curve25519
// See https://datatracker.ietf.org/doc/html/rfc7748#section-6
pub fn derive_shared_secret(mut local PrivateKey, remote PublicKey, opt SharedOpts) ![]u8 {
// TODO: should this check be relaxed ?
// check for safety
local_pubkey := local.public_key()!
if local_pubkey.equal(remote) {
return error('unallowed equal public key between peer')
}
// The local peer generates local private key, local_privkey, generates local public key, local_pubkey.
// and remote peer generates remote private key, ie, remote_privkey,
// with generated remote public key, remote_pubkey.
// Both now share shared = X25519(local_privkey, remote_pubkey) = X25519(remote_privkey, local_pubkey)
// as a shared secret, which is then used as a key or input to a key derivation function.
sec := local.x25519(remote.key)!
// Internally, x25519 has builtin check for zeros result
// but only for non base point branch on x25519_generic routine
if is_zero(sec) {
return error('zeroes shared secret')
}
// you can choose this sec as an input into other key derivator, and pass this sec
// into provided derivator
if opt.should_derive {
// While the shared secret can be used directly, it's often recommended to apply
// a key derivation function (KDF), like HKDF to derive a more robust key for cryptographic operations.
new_sec := opt.derivator.derive(sec, opt.drv_opts)!
if is_zero(new_sec) {
return error('zeroes shared secret after derivation')
}
return new_sec
}
// otherwise, just return the sec as is.
return sec
}
// x25519 returns the result of the scalar multiplication (`scalar` * `point`),
// according to RFC 7748, Section 5. scalar, point and the return value are slices of 32 bytes.
// The functions take a scalar and a `u-coordinate` as inputs and produce a `u-coordinate` as output.
// Although the functions work internally with integers, the inputs and
// outputs are 32-bytes length (for X25519).
// scalar can be generated at random, for example with `crypto.rand` and point should
// be either `base_point` or the output of another `x25519` call.
@[direct_array_access]
pub fn x25519(mut scalar []u8, point []u8) ![]u8 {
// likes the previous comment, we add zeroes point check here
// and reject if it happen.
if is_zero(point) || is_zero(scalar) {
return error('x25519: unallowed zeros/scalar point')
}
mut dst := []u8{len: 32}
// we do bytes clamping here, to make sure scalar was ready to use
clamp(mut scalar)!
return x25519_generic(mut dst, mut scalar, point)
}
@[direct_array_access; inline]
fn x25519_generic(mut dst []u8, mut scalar []u8, point []u8) ![]u8 {
// we dont check arrays length here, its has been checked
// on the underlying scalar_mult routine
if is_base_point(point) {
scalar_base_mult(mut dst, mut scalar)!
// check for base_point result
if is_base_point(dst) {
return error('dst: get base_point')
}
} else {
scalar_mult(mut dst, mut scalar, point)!
// check for zeros point result
if is_zero_point(dst) {
return error('bad input point: low order point')
}
}
return dst
}
// scalar_base_mult performs scalar * base_point
@[direct_array_access]
fn scalar_base_mult(mut dst []u8, mut scalar []u8) ! {
scalar_mult(mut dst, mut scalar, base_point)!
}
// scalar_mult performs scalar multiplicatipn (scalar * point) on the curve25519 curve.
// for performance reason, scalar marked as mutable.
// scalar_mult is the main routine to perform scalar multiplication
// between scalar key and point on the curve25519 curve.
@[direct_array_access; inline]
fn scalar_mult(mut dst []u8, mut scalar []u8, point []u8) ! {
if dst.len != point_size {
return error('bad dst length')
}
if scalar.len != scalar_size {
return error('scalar.lenght != 32')
}
if point.len != point_size {
return error('point.lenght != 32')
}
// Note: we dont clamping scalar here, and responsible to the caller
// to do the clamping. Its assumed scalar has been clamped.
mut x1 := edwards25519.Element{}
mut x2 := edwards25519.Element{}
mut z2 := edwards25519.Element{}
mut x3 := edwards25519.Element{}
mut z3 := edwards25519.Element{}
mut tmp0 := edwards25519.Element{}
mut tmp1 := edwards25519.Element{}
x1.set_bytes(point[..])!
x2.one()
x3.set(x1)
z3.one()
mut swap := 0
for pos := 254; pos >= 0; pos-- {
mut b := scalar[pos / 8] >> u32(pos & 7)
b &= 1
swap = swap ^ int(b)
x2.swap(mut x3, swap)
z2.swap(mut z3, swap)
swap = int(b)
tmp0.subtract(x3, z3)
tmp1.subtract(x2, z2)
x2.add(x2, z2)
z2.add(x3, z3)
z3.multiply(tmp0, x2)
z2.multiply(z2, tmp1)
tmp0.square(tmp1)
tmp1.square(x2)
x3.add(z3, z2)
z2.subtract(z3, z2)
x2.multiply(tmp1, tmp0)
tmp1.subtract(tmp1, tmp0)
z2.square(z2)
z3.mult_32(tmp1, 121666)
x3.square(x3)
tmp0.add(tmp0, z3)
z3.multiply(x1, z2)
z2.multiply(tmp1, tmp0)
}
x2.swap(mut x3, swap)
z2.swap(mut z3, swap)
z2.invert(z2)
x2.multiply(x2, z2)
copy(mut dst, x2.bytes())
}
// Utility helpers
//
@[direct_array_access; inline]
fn is_zero_point(point []u8) bool {
if point.len != point_size {
return false
}
return subtle.constant_time_compare(point, zero_point) == 1
}
@[direct_array_access; inline]
fn is_base_point(point []u8) bool {
if point.len != point_size {
return false
}
return subtle.constant_time_compare(point, base_point) == 1
}
// clamp clears out some bits of seed bytes
@[direct_array_access; inline]
fn clamp(mut seed []u8) ! {
if seed.len != scalar_size {
return error('bad seed sizes for clamp')
}
// According to RFC 7748, for x25519, in order to decode 32 random bytes
// as an integer scalar, set the three least significant bits of the first byte
// and the most significant bit of the last to zero,
// set the second most significant bit of the last byte to 1
//
seed[0] &= 248
seed[31] &= 127
seed[31] |= 64
}
// is_zero returns whether seed is all zeroes in constant time.
fn is_zero(seed []u8) bool {
mut acc := u8(0)
for b in seed {
acc |= b
}
return acc == 0
}