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;
}
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 = s.invert();
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>();
}
}