mirror of
https://github.com/vlang/v.git
synced 2025-09-15 07:22:27 +03:00
412 lines
12 KiB
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
|
|
}
|