use super::arith::Field;
use super::point::{AffinePoint, Curve, Point};
use ic_core::traits::internal::Digest;
use ic_core::traits::internal::Mac;
use ic_core::{ensure, Result, Zeroize};
pub trait EcdsaCurve: Curve + crate::nist::gentable::HasGeneratorTable {
type Digest: Digest;
type Hmac: Mac + Clone;
fn invert_public_scalar(s: &Self::Scalar) -> Self::Scalar {
s.invert()
}
}
const MAX_SCALAR: usize = 66;
const MAX_T: usize = 128;
fn bits2int(t: &[u8], order_bits: usize, out: &mut [u8]) {
let m = order_bits.div_ceil(8);
let shift = m * 8 - order_bits;
for i in 0..m {
let hi = if i == 0 { 0 } else { t[i - 1] };
let carry = if shift == 0 { 0 } else { hi << (8 - shift) };
out[i] = (t[i] >> shift) | carry;
}
}
fn rfc6979_nonce<C: EcdsaCurve>(
private_key: &[u8],
h1: &[u8],
attempt: usize,
) -> Result<C::Scalar> {
let n = C::SCALAR_BYTES;
ensure!(n <= MAX_SCALAR, InvalidParameter, "scalar too wide");
let e = C::scalar_reduce_slice(h1);
let e_octets = e.to_bytes();
let e_octets = e_octets.as_ref();
let tag_len = <C::Hmac as Mac>::TAG_LEN;
let mut v_buf = [0x01u8; MAX_SCALAR];
let mut k_buf = [0x00u8; MAX_SCALAR];
let v = &mut v_buf[..tag_len];
let k = &mut k_buf[..tag_len];
let mut mac = C::Hmac::new(k)?;
mac.update(v);
mac.update(&[0x00]);
mac.update(private_key);
mac.update(e_octets);
k.copy_from_slice(mac.finalize().as_ref());
let mut keyed = C::Hmac::new(k)?;
let mut mac = keyed.clone();
mac.update(v);
v.copy_from_slice(mac.finalize().as_ref());
let mut mac = keyed.clone();
mac.update(v);
mac.update(&[0x01]);
mac.update(private_key);
mac.update(e_octets);
k.copy_from_slice(mac.finalize().as_ref());
keyed = C::Hmac::new(k)?;
let mut mac = keyed.clone();
mac.update(v);
v.copy_from_slice(mac.finalize().as_ref());
let mut found = 0usize;
let mut t_buf = [0u8; MAX_T];
let mut candidate_buf = [0u8; MAX_SCALAR];
for _ in 0..(attempt + 1) * 8 + 16 {
let mut tlen = 0usize;
while tlen * 8 < C::ORDER_BITS {
let mut mac = keyed.clone();
mac.update(v);
v.copy_from_slice(mac.finalize().as_ref());
let take = core::cmp::min(tag_len, MAX_T - tlen);
t_buf[tlen..tlen + take].copy_from_slice(&v[..take]);
tlen += take;
}
bits2int(&t_buf[..tlen], C::ORDER_BITS, &mut candidate_buf);
if let Some(candidate) = C::scalar_from_slice(&candidate_buf[..n]) {
if !bool::from(candidate.is_zero()) {
if found == attempt {
k_buf.zeroize();
v_buf.zeroize();
t_buf.zeroize();
candidate_buf.zeroize();
return Ok(candidate);
}
found += 1;
}
}
let mut mac = keyed.clone();
mac.update(v);
mac.update(&[0x00]);
k.copy_from_slice(mac.finalize().as_ref());
keyed = C::Hmac::new(k)?;
let mut mac = keyed.clone();
mac.update(v);
v.copy_from_slice(mac.finalize().as_ref());
}
k_buf.zeroize();
v_buf.zeroize();
t_buf.zeroize();
candidate_buf.zeroize();
Err(ic_core::err!(
Internal,
"rfc6979 nonce generation did not converge"
))
}
fn load_private_key<C: Curve>(bytes: &[u8]) -> Result<C::Scalar> {
ensure!(
bytes.len() == C::SCALAR_BYTES,
InvalidLength,
"ecdsa private key"
);
let d = C::scalar_from_slice(bytes).ok_or(ic_core::err!(
InvalidParameter,
"ecdsa private key is not less than n"
))?;
ensure!(
!bool::from(d.is_zero()),
InvalidParameter,
"ecdsa private key must not be zero"
);
Ok(d)
}
pub fn public_key<C: EcdsaCurve>(private_key: &[u8], out: &mut [u8]) -> Result<()> {
ic_core::module::operational()?;
ensure!(
out.len() == 1 + 2 * C::FIELD_BYTES,
InvalidLength,
"ecdsa public key buffer"
);
let d = load_private_key::<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: EcdsaCurve>(private_key: &[u8], out: &mut [u8]) -> Result<()> {
ic_core::module::operational()?;
ensure!(
out.len() == 1 + C::FIELD_BYTES,
InvalidLength,
"ecdsa compressed key buffer"
);
let d = load_private_key::<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 sign<C: EcdsaCurve>(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()> {
ic_core::module::operational()?;
let n = C::SCALAR_BYTES;
ensure!(
signature.len() == 2 * n,
InvalidLength,
"ecdsa signature buffer"
);
let d = load_private_key::<C>(private_key)?;
let digest = C::Digest::digest(message);
let h1 = digest.as_ref();
let e = C::scalar_reduce_slice(h1);
for attempt in 0..8 {
let k = rfc6979_nonce::<C>(private_key, h1, attempt)?;
let x = Point::<C>::mul_generator(&k)
.to_affine_x()
.ok_or(ic_core::err!(Internal, "kG is the identity"))?;
let r = C::scalar_reduce_slice(x.to_bytes().as_ref());
if bool::from(r.is_zero()) {
continue;
}
let s = k.invert().mul(&e.add(&r.mul(&d)));
if bool::from(s.is_zero()) {
continue;
}
signature[..n].copy_from_slice(r.to_bytes().as_ref());
signature[n..].copy_from_slice(s.to_bytes().as_ref());
return Ok(());
}
Err(ic_core::err!(Internal, "ecdsa signing did not converge"))
}
pub fn verify<C: EcdsaCurve>(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()> {
ic_core::module::operational()?;
let digest = C::Digest::digest(message);
verify_digest::<C>(public_key, digest.as_ref(), signature)
}
pub const PREHASH_LENS: [usize; 4] = [28, 32, 48, 64];
pub fn verify_prehash<C: EcdsaCurve>(
public_key: &[u8],
digest: &[u8],
signature: &[u8],
) -> Result<()> {
ic_core::module::operational()?;
ensure!(
PREHASH_LENS.contains(&digest.len()),
InvalidLength,
"ecdsa digest must be 28, 32, 48 or 64 bytes"
);
ensure!(
digest.len() >= min_prehash_len::<C>(),
InvalidLength,
"ecdsa digest narrower than the curve's strength"
);
verify_digest::<C>(public_key, digest, signature)
}
pub fn min_prehash_len<C: EcdsaCurve>() -> usize {
core::cmp::min(C::SCALAR_BYTES, 64)
}
fn verify_digest<C: EcdsaCurve>(public_key: &[u8], digest: &[u8], signature: &[u8]) -> Result<()> {
let n = C::SCALAR_BYTES;
ensure!(signature.len() == 2 * n, InvalidLength, "ecdsa signature");
let q = AffinePoint::<C>::from_sec1(public_key).ok_or(ic_core::err!(
MalformedEncoding,
"ecdsa public key is not a curve point"
))?;
let r = C::scalar_from_slice(&signature[..n]).ok_or(ic_core::err!(
MalformedEncoding,
"ecdsa r is not less than n"
))?;
let s = C::scalar_from_slice(&signature[n..]).ok_or(ic_core::err!(
MalformedEncoding,
"ecdsa s is not less than n"
))?;
ensure!(
!bool::from(r.is_zero()) && !bool::from(s.is_zero()),
MalformedEncoding,
"ecdsa r and s must be non-zero"
);
let e = C::scalar_reduce_slice(digest);
let w = C::invert_public_scalar(&s);
let u1 = e.mul(&w);
let u2 = r.mul(&w);
let point = Point::<C>::mul_double(&u1, &Point::<C>::from_affine(&q), &u2);
let r_bytes = r.to_bytes();
let mut xs = [C::Field::ZERO; 2];
let mut count = 0;
if let Some(x) = C::field_from_slice(r_bytes.as_ref()) {
xs[count] = x;
count += 1;
}
if let Some(x) = r_plus_n::<C>(r_bytes.as_ref()).and_then(|b| C::field_from_slice(&b[..n])) {
xs[count] = x;
count += 1;
}
if point.x_is_one_of_vartime(&xs[..count]) {
Ok(())
} else {
Err(ic_core::err!(AuthenticationFailed, "ecdsa"))
}
}
fn r_plus_n<C: Curve>(r: &[u8]) -> Option<[u8; MAX_SCALAR]> {
let len = r.len();
let n_minus_1 = C::Scalar::ZERO.sub(&C::Scalar::ONE).to_bytes();
let n_minus_1 = n_minus_1.as_ref();
let mut out = [0u8; MAX_SCALAR];
let mut carry = 1u16;
for i in (0..len).rev() {
let s = r[i] as u16 + n_minus_1[i] as u16 + carry;
out[i] = s as u8;
carry = s >> 8;
}
(carry == 0).then_some(out)
}
fn is_high_s<C: Curve>(s: &C::Scalar) -> bool {
let bytes = s.to_bytes();
let bytes = bytes.as_ref();
let n_bytes = C::Scalar::ZERO.sub(&C::Scalar::ONE).to_bytes();
let n_bytes = n_bytes.as_ref();
let mut half = [0u8; MAX_SCALAR];
let len = n_bytes.len();
let mut carry = 0u8;
for i in 0..len {
let v = n_bytes[i];
half[i] = (v >> 1) | (carry << 7);
carry = v & 1;
}
for i in 0..len {
if bytes[i] != half[i] {
return bytes[i] > half[i];
}
}
false
}
pub fn normalize_s<C: EcdsaCurve>(signature: &mut [u8]) -> Result<()> {
let n = C::SCALAR_BYTES;
ensure!(signature.len() == 2 * n, InvalidLength, "ecdsa signature");
let s = C::scalar_from_slice(&signature[n..]).ok_or(ic_core::err!(
MalformedEncoding,
"ecdsa s is not less than n"
))?;
if is_high_s::<C>(&s) {
let flipped = C::Scalar::ZERO.sub(&s);
signature[n..].copy_from_slice(flipped.to_bytes().as_ref());
}
Ok(())
}
pub fn has_low_s<C: EcdsaCurve>(signature: &[u8]) -> Result<bool> {
let n = C::SCALAR_BYTES;
ensure!(signature.len() == 2 * n, InvalidLength, "ecdsa signature");
let s = C::scalar_from_slice(&signature[n..]).ok_or(ic_core::err!(
MalformedEncoding,
"ecdsa s is not less than n"
))?;
Ok(!is_high_s::<C>(&s))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::p256::P256;
use crate::p384::P384;
fn reference<C: EcdsaCurve>(x: &[u8], h1: &[u8], attempt: usize) -> std::vec::Vec<u8> {
let mac = |key: &[u8], parts: &[&[u8]]| -> std::vec::Vec<u8> {
let mut m = C::Hmac::new(key).unwrap();
for p in parts {
m.update(p);
}
m.finalize().as_ref().to_vec()
};
let hlen = <C::Hmac as Mac>::TAG_LEN;
let e = C::scalar_reduce_slice(h1).to_bytes();
let mut v = std::vec![0x01u8; hlen];
let mut k = std::vec![0x00u8; hlen];
k = mac(&k, &[&v, &[0x00], x, e.as_ref()]);
v = mac(&k, &[&v]);
k = mac(&k, &[&v, &[0x01], x, e.as_ref()]);
v = mac(&k, &[&v]);
let mut seen = 0;
loop {
v = mac(&k, &[&v]);
let candidate = v.clone();
let ok = C::scalar_from_slice(&candidate).is_some_and(|c| !bool::from(c.is_zero()));
if ok {
if seen == attempt {
return candidate;
}
seen += 1;
}
k = mac(&k, &[&v, &[0x00]]);
v = mac(&k, &[&v]);
}
}
fn nonces_agree<C: EcdsaCurve>() {
let x = std::vec![0x5au8; C::SCALAR_BYTES];
for msg in [&b"sample"[..], b"test", b""] {
let h1 = C::Digest::digest(msg);
for attempt in 0..4 {
let got = rfc6979_nonce::<C>(&x, h1.as_ref(), attempt).unwrap();
let want = reference::<C>(&x, h1.as_ref(), attempt);
assert_eq!(got.to_bytes().as_ref(), &want[..], "attempt {attempt}");
}
}
}
#[test]
fn nonces_after_rejection_follow_rfc_6979() {
nonces_agree::<P256>();
nonces_agree::<P384>();
}
#[test]
#[ignore = "timing; run manually with --release"]
fn where_verification_spends_its_time() {
fn ns(label: &str, iters: u32, mut f: impl FnMut()) {
use std::time::Instant;
let mut best = f64::INFINITY;
for _ in 0..9 {
let t = Instant::now();
for _ in 0..iters {
f();
}
best = best.min(t.elapsed().as_secs_f64() * 1e9 / iters as f64);
}
std::println!("{label:40} {best:10.1} ns");
}
fn curve<C: EcdsaCurve>() {
use core::hint::black_box;
std::println!("{}", C::NAME);
let n = C::SCALAR_BYTES;
let key = std::vec![0x5au8; n];
let mut pk = std::vec![0u8; 1 + 2 * C::FIELD_BYTES];
public_key::<C>(&key, &mut pk).unwrap();
let mut sig = std::vec![0u8; 2 * n];
sign::<C>(&key, b"message", &mut sig).unwrap();
C::prepare_generator_table();
ns("verify", 300, || {
verify::<C>(black_box(&pk), b"message", black_box(&sig)).unwrap();
});
ns("digest", 3000, || {
black_box(C::Digest::digest(black_box(b"message")));
});
ns("decode public key", 3000, || {
black_box(AffinePoint::<C>::from_sec1(black_box(&pk)));
});
let s = C::scalar_from_slice(&sig[n..]).unwrap();
let r = C::scalar_from_slice(&sig[..n]).unwrap();
ns("s^-1 mod n", 3000, || {
black_box(C::invert_public_scalar(&black_box(s)));
});
let w = s.invert();
let u1 = r.mul(&w).mul(&w);
let u2 = r.mul(&w);
ns("u1, u2", 30000, || {
black_box(black_box(r).mul(&black_box(w)));
});
let q = Point::<C>::from_affine(&AffinePoint::<C>::from_sec1(&pk).unwrap());
ns("mul_double", 300, || {
black_box(Point::<C>::mul_double(&black_box(u1), &q, &black_box(u2)));
});
{
use super::super::point::wnaf_vartime;
use super::super::verify::{
joint_vartime, odd_multiples_affine_vartime, G_WIDTH, Q_ODD, Q_WIDTH,
};
ns(" Q's odd multiples, affine", 3000, || {
black_box(odd_multiples_affine_vartime::<C, Q_ODD>(&black_box(q)));
});
let qt: [AffinePoint<C>; Q_ODD] = odd_multiples_affine_vartime(&q);
let gt = C::odd_generator_multiples();
let (ab, bb) = (u1.to_bytes(), u2.to_bytes());
ns(" both non-adjacent forms", 3000, || {
black_box(wnaf_vartime(black_box(ab.as_ref()), G_WIDTH));
black_box(wnaf_vartime(black_box(bb.as_ref()), Q_WIDTH));
});
let (gn, gl) = wnaf_vartime(ab.as_ref(), G_WIDTH);
let (qn, ql) = wnaf_vartime(bb.as_ref(), Q_WIDTH);
let len = gl.max(ql);
let adds = gn[..len]
.iter()
.chain(&qn[..len])
.filter(|d| **d != 0)
.count();
std::println!(" {len} doublings, {adds} additions");
ns(" the chain, fast where there is one", 300, || {
black_box(
C::mul_double_vartime_fast(gt, &qt, &gn[..len], &qn[..len])
.unwrap_or_else(Point::identity),
);
});
ns(" the chain, field operations", 300, || {
black_box(joint_vartime(gt, &qt, &gn[..len], &qn[..len]));
});
}
ns(" u1 * G, table, vartime", 1000, || {
black_box(Point::<C>::mul_generator_vartime(&black_box(u1)));
});
ns(" u2 * Q, wNAF 5", 300, || {
black_box(black_box(q).mul_scalar_vartime(&black_box(u2)));
});
let p = q.double();
ns(" double", 30000, || {
black_box(black_box(p).double());
});
ns(" add_vartime", 30000, || {
black_box(black_box(p).add_vartime(&black_box(q)));
});
let qa = AffinePoint::<C>::from_sec1(&pk).unwrap();
ns(" add_affine_vartime", 30000, || {
black_box(black_box(p).add_affine_vartime(&black_box(qa.x), &black_box(qa.y)));
});
ns(" field inversion", 3000, || {
black_box(black_box(qa.x).invert());
});
let x = qa.x;
ns("x_is_one_of_vartime", 30000, || {
black_box(black_box(p).x_is_one_of_vartime(&[black_box(x), black_box(x)]));
});
}
curve::<P256>();
curve::<P384>();
}
}