// ECDSA signing and verification for P-256 (FIPS 186-4).
// Uses RFC 6979 deterministic nonce generation.
/// ECDSA key pair.
pub struct ECDSAKeyPair {
/// Private key scalar (32 bytes, big-endian).
private_key : Array[UInt]
/// Public key as uncompressed point (65 bytes: 0x04 || x || y).
public_key : Array[UInt]
}
/// ECDSA signature (r, s) each 32 bytes big-endian.
pub(all) struct ECDSASignature {
r : Array[UInt]
s : Array[UInt]
}
fn bytes_to_uint_array(bytes : Bytes) -> Array[UInt] {
let arr : Array[UInt] = []
for i in 0.. Bytes {
let buf : Array[Byte] = []
for value in values {
buf.push((value & 0xFFU).to_byte())
}
Bytes::from_array(buf)
}
/// Generate key pair from a 32-byte private key.
pub fn ecdsa_keygen(private_key_bytes : Array[UInt]) -> Result[ECDSAKeyPair, String] {
if private_key_bytes.length() != 32 {
return Err("Private key must be 32 bytes")
}
let d = sc_from_bytes(private_key_bytes)
if !sc_is_valid(d) {
return Err("Private key out of range")
}
// Q = d * G
let q = point_mul(d, point_generator())
let q_affine = point_to_affine(q)
if q_affine.infinity {
return Err("Invalid private key (produces identity)")
}
let pub_key : Array[UInt] = []
pub_key.push(0x04U) // uncompressed point prefix
let x_bytes = fe_to_bytes(q_affine.x)
let y_bytes = fe_to_bytes(q_affine.y)
for v in x_bytes {
pub_key.push(v)
}
for v in y_bytes {
pub_key.push(v)
}
Ok(ECDSAKeyPair::{ private_key: private_key_bytes, public_key: pub_key })
}
/// Parse a public key from uncompressed format (65 bytes: 0x04 || x || y).
fn parse_public_key(pub_key : Array[UInt]) -> Result[ProjectivePoint, String] {
if pub_key.length() != 65 || pub_key[0] != 0x04U {
return Err("Invalid public key format (expected uncompressed)")
}
let x_bytes : Array[UInt] = []
let y_bytes : Array[UInt] = []
for i in 1..<33 {
x_bytes.push(pub_key[i])
}
for i in 33..<65 {
y_bytes.push(pub_key[i])
}
let x = fe_from_bytes(x_bytes)
let y = fe_from_bytes(y_bytes)
// Verify point is on curve: y^2 = x^3 + ax + b (mod p)
let x_mont = fe_to_mont(x)
let y_mont = fe_to_mont(y)
let y2 = fe_sqr(y_mont)
let x2 = fe_sqr(x_mont)
let x3 = fe_mul(x2, x_mont)
let ax = fe_mul(get_curve_a_mont(), x_mont)
let rhs = fe_add(fe_add(x3, ax), get_curve_b_mont())
if !fe_eq(y2, rhs) {
return Err("Point not on curve")
}
Ok(ProjectivePoint::{ x: x_mont, y: y_mont, z: fe_copy(field_one) })
}
/// RFC 6979 deterministic nonce generation for ECDSA.
/// Produces a deterministic k value from the private key and message hash.
fn rfc6979_generate_k(
private_key : Array[UInt],
hash : FixedArray[UInt],
) -> FixedArray[UInt] {
// hash is 32 bytes (SHA-256 output) as FixedArray[UInt]
let hash_arr : Array[UInt] = []
for i in 0.. Result[ECDSASignature, String] {
if private_key.length() != 32 {
return Err("Private key must be 32 bytes")
}
if message_hash.length() != 32 {
return Err("Message hash must be 32 bytes (SHA-256)")
}
let d = sc_from_bytes(private_key)
if !sc_is_valid(d) {
return Err("Private key out of range")
}
// z = message hash as scalar (truncated to n bits if needed, but SHA-256 is 256 bits = n bits)
let z_bytes : Array[UInt] = []
for i in 0.. Result[ECDSASignature, String] {
let hash = @sha2.Sha256::digest(message)
ecdsa_sign(private_key, hash)
}
/// Verify an ECDSA signature against a message hash and public key.
pub fn ecdsa_verify(
public_key : Array[UInt],
message_hash : FixedArray[UInt],
signature : ECDSASignature,
) -> Result[Bool, String] {
if message_hash.length() != 32 {
return Err("Message hash must be 32 bytes")
}
let q = match parse_public_key(public_key) {
Err(e) => return Err(e)
Ok(p) => p
}
let r = sc_from_bytes(signature.r)
let s = sc_from_bytes(signature.s)
if !sc_is_valid(r) || !sc_is_valid(s) {
return Ok(false)
}
// z = hash as scalar
let z_bytes : Array[UInt] = []
for i in 0.. Result[Bool, String] {
let hash = @sha2.Sha256::digest(message)
ecdsa_verify(public_key, hash, signature)
}
/// Bytes API wrappers
pub fn ecdsa_keygen_bytes(private_key : Bytes) -> Result[ECDSAKeyPair, String] {
ecdsa_keygen(bytes_to_uint_array(private_key))
}
pub fn ecdsa_sign_bytes(
private_key : Bytes,
message : Bytes,
) -> Result[ECDSASignature, String] {
ecdsa_sign_message(bytes_to_uint_array(private_key), bytes_to_uint_array(message))
}
pub fn ecdsa_verify_bytes(
public_key : Bytes,
message : Bytes,
signature : ECDSASignature,
) -> Result[Bool, String] {
ecdsa_verify_message(bytes_to_uint_array(public_key), bytes_to_uint_array(message), signature)
}