use super::arith::Field;
use super::gentable::{Comb, HasGeneratorTable};
use super::point::{AffinePoint, Curve, Point};
use ic_core::{ensure, Result};
fn load_scalar<C: Curve>(bytes: &[u8]) -> Result<C::Scalar> {
ensure!(
bytes.len() == C::SCALAR_BYTES,
InvalidLength,
"ecdh private key"
);
let d = C::scalar_from_slice(bytes).ok_or(ic_core::err!(
InvalidParameter,
"ecdh private key is not less than n"
))?;
ensure!(
!bool::from(d.is_zero()),
InvalidParameter,
"ecdh private key must not be zero"
);
Ok(d)
}
pub fn public_key<C: HasGeneratorTable>(private_key: &[u8], out: &mut [u8]) -> Result<()> {
ic_core::module::operational()?;
ensure!(
out.len() == 1 + 2 * C::FIELD_BYTES,
InvalidLength,
"ecdh public key buffer"
);
let d = load_scalar::<C>(private_key)?;
let q = Point::<C>::mul_generator(&d)
.to_affine()
.ok_or(ic_core::err!(Internal, "public key is the identity"))?;
ensure!(
q.write_uncompressed(out),
Internal,
"public key buffer length disagrees with the encoder"
);
Ok(())
}
pub fn public_key_compressed<C: HasGeneratorTable>(
private_key: &[u8],
out: &mut [u8],
) -> Result<()> {
ic_core::module::operational()?;
ensure!(
out.len() == 1 + C::FIELD_BYTES,
InvalidLength,
"ecdh compressed key buffer"
);
let d = load_scalar::<C>(private_key)?;
let q = Point::<C>::mul_generator(&d)
.to_affine()
.ok_or(ic_core::err!(Internal, "public key is the identity"))?;
ensure!(
q.write_compressed(out),
Internal,
"compressed key buffer length disagrees with the encoder"
);
Ok(())
}
pub fn agree<C: Curve>(private_key: &[u8], peer_public_key: &[u8], out: &mut [u8]) -> Result<()> {
ic_core::module::operational()?;
ensure!(
out.len() == C::FIELD_BYTES,
InvalidLength,
"ecdh shared secret buffer"
);
let d = load_scalar::<C>(private_key)?;
let q = load_peer::<C>(peer_public_key)?;
finish(Point::<C>::from_affine(&q).mul_scalar(&d), out)
}
fn load_peer<C: Curve>(peer_public_key: &[u8]) -> Result<AffinePoint<C>> {
AffinePoint::<C>::from_sec1(peer_public_key).ok_or(ic_core::err!(
InvalidParameter,
"ecdh peer public key is not a valid curve point"
))
}
fn finish<C: Curve>(shared: Point<C>, out: &mut [u8]) -> Result<()> {
let x = shared.to_affine_x().ok_or(ic_core::err!(
InvalidParameter,
"ecdh key agreement produced the identity"
))?;
out.copy_from_slice(x.to_bytes().as_ref());
Ok(())
}
pub struct PreparedPublicKey<C: Curve> {
comb: Comb<C>,
}
impl<C: Curve> PreparedPublicKey<C> {
pub fn new(peer_public_key: &[u8]) -> Result<Self> {
ic_core::module::operational()?;
let q = load_peer::<C>(peer_public_key)?;
Ok(Self {
comb: Comb::new(&Point::from_affine(&q)),
})
}
pub fn agree(&self, private_key: &[u8], out: &mut [u8]) -> Result<()> {
ic_core::module::operational()?;
ensure!(
out.len() == C::FIELD_BYTES,
InvalidLength,
"ecdh shared secret buffer"
);
let d = load_scalar::<C>(private_key)?;
finish(self.comb.mul(&d), out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nist::gentable::{comb_span, TEETH};
use crate::p256::P256;
use crate::p384::P384;
use crate::p521::P521;
use std::vec::Vec;
struct Stream(u64);
impl Stream {
fn fill(&mut self, out: &mut [u8]) {
for b in out {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*b = (self.0 >> 56) as u8;
}
}
}
fn private_key<C: HasGeneratorTable>(s: &mut Stream) -> Vec<u8> {
let mut bytes = C::Scalar::zero_bytes();
loop {
s.fill(bytes.as_mut());
if C::scalar_from_slice(bytes.as_ref()).is_some_and(|k| !bool::from(k.is_zero())) {
return bytes.as_ref().to_vec();
}
}
}
fn public<C: HasGeneratorTable>(sk: &[u8], compressed: bool) -> Vec<u8> {
let f = C::FIELD_BYTES;
if compressed {
let mut pk = std::vec![0u8; 1 + f];
public_key_compressed::<C>(sk, &mut pk).unwrap();
pk
} else {
let mut pk = std::vec![0u8; 1 + 2 * f];
public_key::<C>(sk, &mut pk).unwrap();
pk
}
}
fn edge_scalars<C: Curve>() -> Vec<C::Scalar> {
let mut out = Vec::new();
let (mut small, mut minus) = (C::Scalar::ZERO, C::Scalar::ZERO);
for _ in 0..40 {
small = small.add(&C::Scalar::ONE);
minus = minus.sub(&C::Scalar::ONE);
out.push(small);
out.push(minus);
}
for fill in [0x88u8, 0x77, 0x99, 0xff, 0x0f, 0xf0, 0x5a, 0x84, 0x10] {
let mut bytes = C::Scalar::zero_bytes();
for b in bytes.as_mut() {
*b = fill;
}
out.push(C::Scalar::from_bytes_reduced(&bytes));
}
let m = comb_span::<C>();
for c in 1..TEETH {
let mut p = C::Scalar::ONE;
for _ in 0..5 * c * m {
p = p.add(&p);
}
out.push(p);
out.push(p.sub(&C::Scalar::ONE));
out.push(p.neg());
}
out
}
fn comb_agrees_with_the_ladder<C: HasGeneratorTable>() -> usize {
let mut checked = 0;
let g = Point::<C>::generator();
let other = g.mul_scalar_ladder(&C::Scalar::ONE.add(&C::Scalar::ONE).neg());
for p in [g, other] {
let comb = Comb::new(&p);
for k in edge_scalars::<C>() {
let want = p.mul_scalar_ladder(&k);
assert!(bool::from(comb.mul(&k).ct_eq(&want)), "{}", C::NAME);
checked += 1;
}
}
checked
}
fn exceptional_scalars<C: Curve>(max: usize) -> Vec<C::Scalar> {
use crate::nist::gentable::{digit_count, signed_digits_w};
let m = comb_span::<C>();
let n = digit_count(C::SCALAR_BYTES, 5);
let mut base = Vec::new();
let mut p = C::Scalar::ONE;
for _ in 0..TEETH {
base.push(p);
for _ in 0..5 * m {
p = p.add(&p);
}
}
let multiples = |b: C::Scalar| {
let mut out = Vec::new();
let mut acc = b.neg().add(&b.neg()).add(&b.neg()).add(&b.neg());
for _ in 0..2 {
acc = acc.add(&acc);
}
for _ in 0..33 {
out.push(acc);
acc = acc.add(&b);
}
out
};
let (b1, b2, b3) = (multiples(base[1]), multiples(base[2]), multiples(base[3]));
let mut found = Vec::new();
for (di, d) in (-16i8..=16).enumerate() {
if d == 0 {
continue;
}
for (r2i, r2) in (-16i8..=16).enumerate() {
for (r3i, r3) in (-16i8..=16).enumerate() {
let k = b1[di].add(&b1[di]).add(&b2[r2i]).add(&b3[r3i]);
let digits = signed_digits_w(k.to_bytes().as_ref(), 5);
let top = |c: usize| if c * m < n { digits[c * m] } else { 0 };
if top(1) == d && top(2) == r2 && top(3) == r3 && !bool::from(k.is_zero()) {
found.push(k);
if found.len() == max {
return found;
}
}
}
}
}
found
}
#[test]
fn the_comb_is_right_where_only_the_complete_addition_is() {
fn run<C: HasGeneratorTable>() -> usize {
let g = Point::<C>::generator();
let comb = Comb::new(&g);
let scalars = exceptional_scalars::<C>(8);
for k in &scalars {
let want = g.mul_scalar_ladder(k);
assert!(bool::from(comb.mul(k).ct_eq(&want)), "{}", C::NAME);
}
scalars.len()
}
assert_eq!(run::<P256>(), 8);
assert_eq!(run::<P384>(), 8);
assert_eq!(run::<P521>(), 8);
}
#[test]
fn the_comb_agrees_with_the_ladder_on_edge_scalars() {
let checked = comb_agrees_with_the_ladder::<P256>()
+ comb_agrees_with_the_ladder::<P384>()
+ comb_agrees_with_the_ladder::<P521>();
assert_eq!(checked, 2 * 3 * (80 + 9 + 3 * (TEETH - 1)));
}
fn entries_are_small<C: Curve>() -> usize {
let m = comb_span::<C>();
assert!(5 * (TEETH - 1) * m + 4 <= C::ORDER_BITS - 3, "{}", C::NAME);
let n = super::super::gentable::digit_count(C::SCALAR_BYTES, 5);
(0..m).filter(|j| 5 * (n - j) >= C::ORDER_BITS).count()
}
#[test]
fn comb_entries_are_small_against_the_order() {
assert_eq!(entries_are_small::<P256>(), 1);
assert_eq!(entries_are_small::<P384>(), 1);
assert_eq!(entries_are_small::<P521>(), 2);
}
fn prepared_matches_unprepared<C: HasGeneratorTable>(seed: u64, rounds: usize) -> usize {
let mut s = Stream(seed);
let f = C::FIELD_BYTES;
let mut checked = 0;
for _ in 0..rounds {
let peer = public::<C>(&private_key::<C>(&mut s), false);
let peer_c = public::<C>(&private_key::<C>(&mut s), true);
for pk in [&peer, &peer_c] {
let prepared = PreparedPublicKey::<C>::new(pk).unwrap();
for _ in 0..3 {
let sk = private_key::<C>(&mut s);
let (mut want, mut got) = (std::vec![0u8; f], std::vec![0u8; f]);
agree::<C>(&sk, pk, &mut want).unwrap();
prepared.agree(&sk, &mut got).unwrap();
assert_eq!(want, got, "{}", C::NAME);
checked += 1;
}
}
}
checked
}
#[test]
fn prepared_agreement_matches_unprepared_on_random_keys() {
let checked = prepared_matches_unprepared::<P256>(1, 40)
+ prepared_matches_unprepared::<P384>(2, 40)
+ prepared_matches_unprepared::<P521>(3, 10);
assert_eq!(checked, 6 * 90);
}
fn prepared_edges<C: HasGeneratorTable>() {
let f = C::FIELD_BYTES;
let mut s = Stream(7);
let peer_sk = private_key::<C>(&mut s);
let peer = public::<C>(&peer_sk, false);
let prepared = PreparedPublicKey::<C>::new(&peer).unwrap();
for k in edge_scalars::<C>() {
let sk = k.to_bytes();
let (mut want, mut got) = (std::vec![0u8; f], std::vec![0u8; f]);
agree::<C>(sk.as_ref(), &peer, &mut want).unwrap();
prepared.agree(sk.as_ref(), &mut got).unwrap();
assert_eq!(want, got, "{}", C::NAME);
let mut back = std::vec![0u8; f];
agree::<C>(&peer_sk, &public::<C>(sk.as_ref(), false), &mut back).unwrap();
assert_eq!(back, got, "{}", C::NAME);
}
let mut one = C::Scalar::zero_bytes();
let last = one.as_ref().len() - 1;
one.as_mut()[last] = 1;
let mut got = std::vec![0u8; f];
prepared.agree(one.as_ref(), &mut got).unwrap();
assert_eq!(got, peer[1..1 + f]);
}
#[test]
fn prepared_agreement_handles_edge_private_keys() {
prepared_edges::<P256>();
prepared_edges::<P384>();
prepared_edges::<P521>();
}
fn prepared_refusals<C: HasGeneratorTable>() {
let f = C::FIELD_BYTES;
let mut s = Stream(11);
let sk = private_key::<C>(&mut s);
let good = public::<C>(&private_key::<C>(&mut s), false);
let good_c = public::<C>(&private_key::<C>(&mut s), true);
let mut bad: Vec<Vec<u8>> = std::vec![
Vec::new(),
std::vec![0u8],
std::vec![0u8; 1 + 2 * f],
good[..good.len() - 1].to_vec(),
];
let mut longer = good.clone();
longer.push(0);
bad.push(longer);
for tag in [0x00u8, 0x01, 0x05, 0x06, 0x07] {
let mut b = good.clone();
b[0] = tag;
bad.push(b);
}
let mut b = good.clone();
b[2 * f] ^= 1;
bad.push(b);
let p_bytes = {
let mut bytes = C::Field::ZERO.sub(&C::Field::ONE).to_bytes();
let raw = bytes.as_mut();
let mut i = raw.len();
loop {
i -= 1;
let (v, carry) = raw[i].overflowing_add(1);
raw[i] = v;
if !carry {
break;
}
}
raw.to_vec()
};
for coord in [p_bytes.clone(), std::vec![0xffu8; f]] {
let mut x = good.clone();
x[1..1 + f].copy_from_slice(&coord);
bad.push(x);
let mut y = good.clone();
y[1 + f..].copy_from_slice(&coord);
bad.push(y);
let mut c = good_c.clone();
c[1..].copy_from_slice(&coord);
bad.push(c);
}
let mut x = C::Field::ONE;
loop {
let rhs = x.square().mul(&x).sub(&x.triple()).add(&C::B);
let root = C::sqrt(&rhs);
if root.square() != rhs {
break;
}
x = x.add(&C::Field::ONE);
}
let mut c = std::vec![0x02u8];
c.extend_from_slice(x.to_bytes().as_ref());
bad.push(c);
let mut out = std::vec![0u8; f];
for b in &bad {
assert!(
agree::<C>(&sk, b, &mut out).is_err(),
"{}: agree accepted {b:02x?}",
C::NAME
);
assert!(
PreparedPublicKey::<C>::new(b).is_err(),
"{}: new accepted {b:02x?}",
C::NAME
);
}
let prepared = PreparedPublicKey::<C>::new(&good).unwrap();
let zero = std::vec![0u8; C::SCALAR_BYTES];
let n_bytes = {
let mut bytes = C::Scalar::ONE.neg().to_bytes();
let raw = bytes.as_mut();
let mut i = raw.len();
loop {
i -= 1;
let (v, carry) = raw[i].overflowing_add(1);
raw[i] = v;
if !carry {
break;
}
}
raw.to_vec()
};
for k in [
zero,
n_bytes,
std::vec![0xffu8; C::SCALAR_BYTES],
sk[1..].to_vec(),
] {
assert!(prepared.agree(&k, &mut out).is_err(), "{}", C::NAME);
assert!(agree::<C>(&k, &good, &mut out).is_err(), "{}", C::NAME);
}
assert!(prepared.agree(&sk, &mut std::vec![0u8; f - 1]).is_err());
assert!(prepared.agree(&sk, &mut std::vec![0u8; f + 1]).is_err());
}
#[test]
fn prepared_keys_refuse_what_agree_refuses() {
prepared_refusals::<P256>();
prepared_refusals::<P384>();
prepared_refusals::<P521>();
}
}