[dependencies]p256 = { version = "=0.14.0", features = ["ecdsa", "pem"] } use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};use p256::pkcs8::DecodePublicKey; fn main() -> Result<(), Box<dyn std::error::Error>> { // "BEGIN PUBLIC KEY" (SPKI); a key on any other curve is rejected right here let public_key = VerifyingKey::from_public_key_pem(&std::fs::read_to_string("pub.pem")?)?; let message = std::fs::read("message.txt")?; // ASN.1 DER, as OpenSSL, Java and Go produce it; malformed bytes fail to parse let signature = Signature::from_der(&std::fs::read("sig.der")?)?; // verify() hashes the message with SHA-256 itself match public_key.verify(&message, &signature) { Ok(()) => println!("valid"), Err(_) => println!("INVALID"), } Ok(())} // r‖s from a JWS or WebCrypto: exactly 64 bytes on P-256, no DER framinglet raw = Signature::from_slice(&std::fs::read("sig.raw")?)?;println!("{}", if public_key.verify(&message, &raw).is_ok() { "valid" } else { "INVALID" }); How it works
VerifyingKey::from_public_key_pemcomes from theDecodePublicKeytrait and reads SPKI (BEGIN PUBLIC KEY). The type is curve-specific: a P-384 key fails to load asp256::ecdsa::VerifyingKey.Signature::from_derparses strict ASN.1 DER and checks that r and s are valid scalars;Signature::from_slicetakes exactly 64 bytes ofr‖s.verifyfrom theVerifiertrait hashes the message with SHA-256 — the hash bound to P-256 in this crate — and returnsOk(())or an opaquesignature::Error.- Both encodings parse into the same
Signaturevalue, so oneverifycall serves both; the run checks DER, raw, and a message with one changed byte.
Gotchas
- Errors are deliberately opaque: a malformed signature, a 70-byte buffer passed to
from_sliceand a wrong signature all display assignature error. Log which step failed (parse or verify) yourself. p256accepts high-S signatures, butk256(secp256k1) rejects them inverify, following Bitcoin's rule. A signature that verifies withp256code can fail withk256code purely because ofs— callnormalize_s()on the signer side.- With
ring(the crypto behind rustls),UnparsedPublicKeyexpects the bare 65-byte SEC1 point, not SPKI: passing the 91-byte SPKI DER fails withUnspecified. PickECDSA_P256_SHA256_ASN1for DER andECDSA_P256_SHA256_FIXEDforr‖s. - Loading a
BEGIN CERTIFICATEfile withfrom_public_key_pemfails: parse the certificate (for example withx509-parser) and take its SubjectPublicKeyInfo.