use super::arith::Field;
use super::point::{AffinePoint, Curve, Point};
use ic_core::traits::{Digest, Mac};
use ic_core::{ensure, Result, Zeroize};
pub trait EcdsaCurve: Curve + crate::nist::gentable::HasGeneratorTable {
type Digest: Digest;
type Hmac: Mac;
}
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 t = C::Hmac::mac(k, v)?;
v.copy_from_slice(t.as_ref());
let mut mac = C::Hmac::new(k)?;
mac.update(v);
mac.update(&[0x01]);
mac.update(private_key);
mac.update(e_octets);
k.copy_from_slice(mac.finalize().as_ref());
let t = C::Hmac::mac(k, v)?;
v.copy_from_slice(t.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 block = C::Hmac::mac(k, v)?;
v.copy_from_slice(block.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 = C::Hmac::new(k)?;
mac.update(v);
mac.update(&[0x00]);
k.copy_from_slice(mac.finalize().as_ref());
let t = C::Hmac::mac(k, v)?;
v.copy_from_slice(t.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<()> {
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<()> {
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<()> {
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 point = Point::<C>::mul_generator(&k)
.to_affine()
.ok_or(ic_core::err!(Internal, "kG is the identity"))?;
let r = C::scalar_reduce_slice(point.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<()> {
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<()> {
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 affine = point
.to_affine()
.ok_or(ic_core::err!(AuthenticationFailed, "ecdsa"))?;
let v = C::scalar_reduce_slice(affine.x.to_bytes().as_ref());
if bool::from(v.ct_eq(&r)) {
Ok(())
} else {
Err(ic_core::err!(AuthenticationFailed, "ecdsa"))
}
}
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))
}