Skip to main content

tecdsa_core/
curve.rs

1//! Elliptic-curve helpers over secp256k1.
2use alloc::vec::Vec;
3use k256::{
4    AffinePoint, ProjectivePoint, Scalar, Secp256k1, U256,
5    elliptic_curve::{
6        PrimeField,
7        bigint::U512,
8        group::{Group, GroupEncoding},
9        ops::Reduce,
10        point::AffineCoordinates,
11    },
12    hash2curve::GroupDigest,
13};
14
15use crate::error::{Error, Result};
16
17/// Domain separation tag for `h` and `f` (spec §10.3).
18pub const H2C_DST: &[u8] = b"tecdsa-1ole/v1/h2c/secp256k1_XMD:SHA-256_SSWU_RO_";
19
20/// `𝒳(R)`: x-coordinate of an affine point reduced modulo the group order.
21pub fn x_coord(point: &ProjectivePoint) -> Scalar {
22    let x = point.to_affine().x();
23    <Scalar as Reduce<U256>>::reduce(&U256::from_be_slice(x.as_slice()))
24}
25
26/// Hash-to-curve generators `h` and `f` bound to `sid` (spec §10.3).
27pub fn derive_h_f(sid: &[u8]) -> Result<(ProjectivePoint, ProjectivePoint)> {
28    let derive = |label: &[u8]| {
29        Secp256k1::hash_from_bytes(&[label, sid], &[H2C_DST]).map_err(|_| Error::InvalidEncoding)
30    };
31    Ok((derive(b"h")?, derive(b"f")?))
32}
33
34/// 33-byte compressed SEC1 encoding.
35pub fn encode_point(p: &ProjectivePoint) -> [u8; 33] {
36    let mut out = [0u8; 33];
37    out.copy_from_slice(p.to_affine().to_bytes().as_ref());
38    out
39}
40
41/// Decodes a compressed point; rejects off-curve encodings and the identity.
42pub fn decode_point(bytes: &[u8]) -> Result<ProjectivePoint> {
43    let arr: [u8; 33] = bytes.try_into().map_err(|_| Error::InvalidEncoding)?;
44    // Canonical form only: SEC1 also defines tags 0x00/0x04/0x05 which the decoder may accept.
45    if arr[0] != 0x02 && arr[0] != 0x03 {
46        return Err(Error::InvalidEncoding);
47    }
48    let p = Option::<AffinePoint>::from(AffinePoint::from_bytes(&arr.into()))
49        .ok_or(Error::InvalidEncoding)?;
50    let p = ProjectivePoint::from(p);
51    if is_identity(&p) {
52        return Err(Error::InvalidEncoding);
53    }
54    Ok(p)
55}
56
57/// 32-byte big-endian scalar encoding.
58pub fn encode_scalar(s: &Scalar) -> [u8; 32] {
59    let mut out = [0u8; 32];
60    out.copy_from_slice(s.to_repr().as_ref());
61    out
62}
63
64/// Decodes a canonical scalar (< q).
65pub fn decode_scalar(bytes: &[u8]) -> Result<Scalar> {
66    let arr: [u8; 32] = bytes.try_into().map_err(|_| Error::InvalidEncoding)?;
67    Option::<Scalar>::from(Scalar::from_repr(arr.into())).ok_or(Error::InvalidEncoding)
68}
69
70/// Uniform non-zero scalar.
71pub fn random_nonzero_scalar<R: k256::elliptic_curve::rand_core::TryRng + ?Sized>(
72    rng: &mut R,
73) -> Scalar {
74    loop {
75        let mut wide = Vec::from([0u8; 64]);
76        if rng.try_fill_bytes(&mut wide).is_err() {
77            continue;
78        }
79        let s = <Scalar as Reduce<U512>>::reduce(&U512::from_be_slice(&wide));
80        if !bool::from(k256::elliptic_curve::Field::is_zero(&s)) {
81            return s;
82        }
83    }
84}
85
86/// `true` for the group generator multiple that is not the identity.
87pub fn is_identity(p: &ProjectivePoint) -> bool {
88    bool::from(p.is_identity())
89}
90
91/// The group order `q` as an integer.
92pub fn order() -> crypto_bigint::BoxedUint {
93    use k256::elliptic_curve::Curve;
94    let be = <Secp256k1 as Curve>::ORDER.to_be_bytes();
95    crypto_bigint::BoxedUint::from_be_slice_vartime(be.as_ref())
96}
97
98/// Reduces a signed integer modulo `q` (constant-time in the value).
99pub fn sbig_to_scalar(x: &crate::arith::sbig::SBig) -> Result<Scalar> {
100    let q = Option::<crypto_bigint::NonZero<crypto_bigint::BoxedUint>>::from(order().to_nz())
101        .ok_or(Error::InvalidModulus)?;
102    let r = x.rem_euclid(&q);
103    decode_scalar(&crate::transcript::uint_to_be(&r, 32)?)
104}