#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::missing_assert_message,
clippy::indexing_slicing,
reason = "test module: panics are acceptable and assertions are self-describing"
)]
use alloc::format;
use crate::{SignError, SignOutput};
const SECP_KEY_HEX: &str = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318";
const ED25519_KEY_HEX: &str = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";
const ED25519_PUBKEY_HEX: &str = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
const SCHNORR_KEY_HEX: &str = "7f7ff03d123792d6ac594bfa67bf6d0c0ab55b6b1fdb6249303fe861f1ccba9a";
const SCHNORR_XONLY_HEX: &str = "17162c921dc4d2518f9a101db33695df1afb56ab82f5ff3e5da6eec3ca5cd917";
const TEST_DIGEST: [u8; 32] = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
];
#[test]
fn sign_error_is_standard_error() {
fn assert_std_error<E: core::error::Error>() {}
assert_std_error::<SignError>();
}
#[cfg(feature = "secp256k1")]
mod secp256k1 {
use super::*;
use crate::Secp256k1Signer;
fn fix() -> Secp256k1Signer {
Secp256k1Signer::from_hex(SECP_KEY_HEX).unwrap()
}
#[test]
fn from_hex_accepts_0x_prefix_and_rejects_wrong_length() {
let a = Secp256k1Signer::from_hex(SECP_KEY_HEX).unwrap();
let b = Secp256k1Signer::from_hex(&format!("0x{SECP_KEY_HEX}")).unwrap();
assert_eq!(a.compressed_public_key(), b.compressed_public_key());
assert!(matches!(
Secp256k1Signer::from_hex("deadbeef"),
Err(SignError::InvalidKey(_))
));
assert!(matches!(
Secp256k1Signer::from_hex(&"aa".repeat(33)),
Err(SignError::InvalidKey(_))
));
assert!(matches!(
Secp256k1Signer::from_hex("not-hex!!"),
Err(SignError::InvalidKey(_))
));
}
#[test]
fn from_bytes_rejects_zero_and_curve_order() {
assert!(matches!(
Secp256k1Signer::from_bytes(&[0u8; 32]),
Err(SignError::InvalidKey(_))
));
let n: [u8; 32] = [
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c,
0xd0, 0x36, 0x41, 0x41,
];
assert!(matches!(
Secp256k1Signer::from_bytes(&n),
Err(SignError::InvalidKey(_))
));
}
#[test]
fn public_key_widths() {
let s = fix();
let compressed = s.compressed_public_key();
let uncompressed = s.uncompressed_public_key();
assert_eq!(compressed.len(), 33);
assert_eq!(uncompressed.len(), 65);
assert!(compressed[0] == 0x02 || compressed[0] == 0x03);
assert_eq!(uncompressed[0], 0x04);
assert_eq!(&compressed[1..33], &uncompressed[1..33]);
}
#[test]
fn sign_prehash_recoverable_output_is_65_bytes_with_raw_parity() {
let s = fix();
let out = s.sign_prehash_recoverable(&TEST_DIGEST).unwrap();
let bytes = out.to_bytes();
assert_eq!(bytes.len(), 65, "r || s || v");
let v = out.v().expect("Ecdsa variant must expose v");
assert!(v == 0 || v == 1, "raw parity, not wire-format header");
assert_eq!(bytes[32] >> 7, 0, "k256 returns low-S scalars");
}
#[test]
fn sign_prehash_der_output_is_asn1_der() {
let s = fix();
let out = s.sign_prehash_der(&TEST_DIGEST).unwrap();
match out {
SignOutput::EcdsaDer(der) => {
assert_eq!(der[0], 0x30, "DER SEQUENCE tag");
assert!(
(8..=72).contains(&der.len()),
"DER ECDSA is typically 70-72 bytes, got {}",
der.len(),
);
}
other => panic!("expected EcdsaDer, got {other:?}"),
}
}
#[test]
fn verify_prehash_accepts_strict_compact_only() {
let s = fix();
let out = s.sign_prehash_recoverable(&TEST_DIGEST).unwrap();
let bytes = out.to_bytes();
let mut compact = [0u8; 64];
compact.copy_from_slice(&bytes[..64]);
let mut recoverable = [0u8; 65];
recoverable.copy_from_slice(&bytes);
s.verify_prehash(&TEST_DIGEST, &compact).unwrap();
s.verify_prehash_recoverable(&TEST_DIGEST, &recoverable)
.unwrap();
s.verify_prehash_any(&TEST_DIGEST, &compact[..]).unwrap();
s.verify_prehash_any(&TEST_DIGEST, &recoverable[..])
.unwrap();
assert!(matches!(
s.verify_prehash_any(&TEST_DIGEST, &[0u8; 63]),
Err(SignError::InvalidSignature(_))
));
assert!(matches!(
s.verify_prehash_any(&TEST_DIGEST, &[0u8; 66]),
Err(SignError::InvalidSignature(_))
));
}
#[test]
fn verify_rejects_wrong_digest_and_tampered_signature() {
let s = fix();
let out = s.sign_prehash_recoverable(&TEST_DIGEST).unwrap();
let bytes = out.to_bytes();
let mut sig = [0u8; 64];
sig.copy_from_slice(&bytes[..64]);
let mut wrong = TEST_DIGEST;
wrong[0] ^= 1;
assert!(s.verify_prehash(&wrong, &sig).is_err());
let mut tampered = sig;
tampered[32] ^= 1;
assert!(s.verify_prehash(&TEST_DIGEST, &tampered).is_err());
}
#[test]
fn verify_prehash_der_round_trip() {
let s = fix();
let out = s.sign_prehash_der(&TEST_DIGEST).unwrap();
let SignOutput::EcdsaDer(der) = out else {
panic!("expected EcdsaDer");
};
s.verify_prehash_der(&TEST_DIGEST, &der).unwrap();
}
#[cfg(feature = "getrandom")]
#[test]
fn try_random_generates_distinct_usable_signers() {
let a = Secp256k1Signer::try_random().unwrap();
let b = Secp256k1Signer::try_random().unwrap();
assert_ne!(a.compressed_public_key(), b.compressed_public_key());
let out = a.sign_prehash_recoverable(&TEST_DIGEST).unwrap();
let mut sig = [0u8; 64];
sig.copy_from_slice(&out.to_bytes()[..64]);
a.verify_prehash(&TEST_DIGEST, &sig).unwrap();
}
#[test]
fn debug_does_not_leak_key_material() {
let s = fix();
let debug = format!("{s:?}");
assert!(debug.contains("[REDACTED]"));
assert!(!debug.contains(&SECP_KEY_HEX[..8]));
}
}
#[cfg(feature = "ed25519")]
mod ed25519 {
use super::*;
use crate::Ed25519Signer;
fn fix() -> Ed25519Signer {
Ed25519Signer::from_hex(ED25519_KEY_HEX).unwrap()
}
#[test]
fn rfc8032_tv1_public_key() {
assert_eq!(fix().public_key_hex(), ED25519_PUBKEY_HEX);
}
#[test]
fn from_bytes_is_infallible_but_from_hex_rejects_bad_length() {
let bytes = [0u8; 32]; assert!(Ed25519Signer::from_bytes(&bytes).is_ok());
assert!(matches!(
Ed25519Signer::from_hex("aabb"),
Err(SignError::InvalidKey(_))
));
assert!(matches!(
Ed25519Signer::from_hex(&"aa".repeat(33)),
Err(SignError::InvalidKey(_))
));
}
#[test]
fn sign_output_is_64_bytes_with_no_v() {
let s = fix();
let out = s.sign_output(b"msg");
let bytes = out.to_bytes();
assert_eq!(bytes.len(), 64);
assert!(out.v().is_none());
assert!(out.public_key().is_none(), "plain Ed25519 carries no pk");
}
#[test]
fn sign_output_with_pubkey_attaches_32_byte_key() {
let s = fix();
let out = s.sign_output_with_pubkey(b"msg");
let bytes = out.to_bytes();
assert_eq!(bytes.len(), 64);
let pk = out.public_key().expect("Ed25519WithPubkey carries pk");
assert_eq!(pk.len(), 32);
assert_eq!(hex::encode(pk), ED25519_PUBKEY_HEX);
}
#[test]
fn verify_rejects_tampered_and_wrong_message() {
let s = fix();
let msg = b"authentic";
let out = s.sign_output(msg);
let bytes = out.to_bytes();
s.verify(msg, &bytes).unwrap();
assert!(s.verify(b"different", &bytes).is_err());
let mut tampered = bytes;
tampered[0] ^= 1;
assert!(s.verify(msg, &tampered).is_err());
assert!(matches!(
s.verify(msg, &[0u8; 63]),
Err(SignError::InvalidSignature(_))
));
}
#[cfg(feature = "getrandom")]
#[test]
fn try_random_generates_distinct_usable_signers() {
let a = Ed25519Signer::try_random().unwrap();
let b = Ed25519Signer::try_random().unwrap();
assert_ne!(a.public_key_bytes(), b.public_key_bytes());
let out = a.sign_output(b"smoke");
a.verify(b"smoke", &out.to_bytes()).unwrap();
}
#[test]
fn debug_does_not_leak_key_material() {
let s = fix();
let debug = format!("{s:?}");
assert!(debug.contains("[REDACTED]"));
assert!(!debug.contains(&ED25519_KEY_HEX[..8]));
}
}
#[cfg(feature = "schnorr")]
mod schnorr {
use super::*;
use crate::SchnorrSigner;
fn fix() -> SchnorrSigner {
SchnorrSigner::from_hex(SCHNORR_KEY_HEX).unwrap()
}
#[test]
fn nip06_tv1_xonly_public_key() {
assert_eq!(fix().xonly_public_key_hex(), SCHNORR_XONLY_HEX);
}
#[test]
fn to_bytes_returns_original_scalar_not_negated() {
let s = fix();
let raw = s.to_bytes();
assert_eq!(hex::encode(raw), SCHNORR_KEY_HEX);
}
#[test]
fn from_bytes_rejects_zero_scalar() {
assert!(matches!(
SchnorrSigner::from_bytes(&[0u8; 32]),
Err(SignError::InvalidKey(_))
));
}
#[test]
fn sign_is_deterministic_and_verifies() {
let s = fix();
let msg = b"BIP-340 deterministic";
let a = s.sign(msg).unwrap();
let b = s.sign(msg).unwrap();
assert_eq!(a.to_bytes(), b.to_bytes(), "aux_rand = 0 is deterministic");
s.verify(msg, &a.to_bytes()).unwrap();
}
#[test]
fn sign_prehash_rejects_non_32_byte_input() {
let s = fix();
assert!(matches!(
s.sign_prehash(&[0u8; 31]),
Err(SignError::InvalidMessage(_))
));
assert!(matches!(
s.sign_prehash(&[0u8; 33]),
Err(SignError::InvalidMessage(_))
));
assert!(s.sign_prehash(&[0u8; 32]).is_ok());
}
#[test]
fn sign_output_layout_matches_schnorr_variant() {
let s = fix();
let out = s.sign(b"layout").unwrap();
match out {
SignOutput::Schnorr {
signature,
xonly_public_key,
} => {
assert_eq!(signature.len(), 64);
assert_eq!(hex::encode(xonly_public_key), SCHNORR_XONLY_HEX);
}
other => panic!("expected Schnorr variant, got {other:?}"),
}
}
#[cfg(feature = "getrandom")]
#[test]
fn try_random_generates_distinct_usable_signers() {
let a = SchnorrSigner::try_random().unwrap();
let b = SchnorrSigner::try_random().unwrap();
assert_ne!(a.xonly_public_key(), b.xonly_public_key());
let out = a.sign(b"smoke").unwrap();
a.verify(b"smoke", &out.to_bytes()).unwrap();
}
#[test]
fn debug_does_not_leak_key_material() {
let s = fix();
let debug = format!("{s:?}");
assert!(debug.contains("[REDACTED]"));
assert!(!debug.contains(&SCHNORR_KEY_HEX[..8]));
}
}
mod sign_output {
use super::*;
#[test]
fn ecdsa_to_bytes_is_r_s_v_65() {
let out = SignOutput::Ecdsa {
signature: [0xAB; 64],
v: 0x1B,
};
let b = out.to_bytes();
assert_eq!(b.len(), 65);
assert!(b[..64].iter().all(|&x| x == 0xAB));
assert_eq!(b[64], 0x1B);
assert_eq!(out.v(), Some(0x1B));
assert!(out.public_key().is_none());
}
#[test]
fn ecdsa_der_to_bytes_passes_through_der() {
let der: [u8; 5] = [0x30, 0x03, 0x02, 0x01, 0x42];
let out = SignOutput::EcdsaDer(der.to_vec());
assert_eq!(out.to_bytes(), der);
assert!(out.v().is_none());
}
#[test]
fn ed25519_to_bytes_is_64_bytes() {
let out = SignOutput::Ed25519([0xCD; 64]);
assert_eq!(out.to_bytes().len(), 64);
assert!(out.v().is_none());
assert!(out.public_key().is_none());
}
#[test]
fn ed25519_with_pubkey_exposes_only_pubkey() {
let out = SignOutput::Ed25519WithPubkey {
signature: [0xEF; 64],
public_key: [0x12; 32],
};
assert_eq!(out.to_bytes().len(), 64, "to_bytes returns the sig only");
assert!(out.v().is_none());
assert_eq!(out.public_key(), Some(&[0x12u8; 32][..]));
}
#[test]
fn schnorr_exposes_only_xonly_pubkey() {
let out = SignOutput::Schnorr {
signature: [0xAA; 64],
xonly_public_key: [0x99; 32],
};
assert_eq!(out.to_bytes().len(), 64);
assert!(out.v().is_none());
assert_eq!(out.public_key(), Some(&[0x99u8; 32][..]));
}
#[test]
fn to_hex_matches_to_bytes() {
let out = SignOutput::Ecdsa {
signature: [0xFF; 64],
v: 0x80,
};
assert_eq!(out.to_hex(), hex::encode(out.to_bytes()));
}
}
#[cfg(feature = "ed25519")]
mod ed25519_rfc8032 {
use crate::Ed25519Signer;
const TV1_SK: &str = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";
const TV1_PK: &str = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
const TV1_MSG: &str = "";
const TV1_SIG: &str = "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b";
const TV2_SK: &str = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb";
const TV2_PK: &str = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c";
const TV2_MSG_HEX: &str = "72";
const TV2_SIG: &str = "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00";
const TV3_SK: &str = "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7";
const TV3_PK: &str = "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025";
const TV3_MSG_HEX: &str = "af82";
const TV3_SIG: &str = "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a";
fn run(sk_hex: &str, pk_hex: &str, msg_hex: &str, sig_hex: &str) {
let signer = Ed25519Signer::from_hex(sk_hex).unwrap();
let msg = hex::decode(msg_hex).unwrap();
assert_eq!(
signer.public_key_hex(),
pk_hex,
"public key must match RFC 8032 vector",
);
let sig = signer.sign_raw(&msg).to_bytes();
assert_eq!(hex::encode(sig), sig_hex, "signature mismatch vs RFC 8032");
signer.verify(&msg, &sig).unwrap();
let mut bad = sig;
bad[0] ^= 0x01;
assert!(signer.verify(&msg, &bad).is_err());
}
#[test]
fn test1_empty_message_matches_rfc8032_kat() {
run(TV1_SK, TV1_PK, TV1_MSG, TV1_SIG);
}
#[test]
fn test2_single_byte_message_matches_rfc8032_kat() {
run(TV2_SK, TV2_PK, TV2_MSG_HEX, TV2_SIG);
}
#[test]
fn test3_two_byte_message_matches_rfc8032_kat() {
run(TV3_SK, TV3_PK, TV3_MSG_HEX, TV3_SIG);
}
}
#[cfg(feature = "schnorr")]
mod schnorr_bip340 {
use k256::schnorr::{Signature as K256Sig, SigningKey, VerifyingKey};
use crate::SchnorrSigner;
fn sign_primitive(sk_hex: &str, aux_hex: &str, msg_hex: &str) -> [u8; 64] {
let sk_bytes = hex::decode(sk_hex).unwrap();
let aux_bytes = hex::decode(aux_hex).unwrap();
let msg = hex::decode(msg_hex).unwrap();
let key = SigningKey::from_bytes(&sk_bytes).unwrap();
let aux: [u8; 32] = aux_bytes.try_into().unwrap();
key.sign_raw(&msg, &aux).unwrap().to_bytes()
}
fn check_sign_and_verify(
index: usize,
sk_hex: &str,
pk_hex: &str,
aux_hex: &str,
msg_hex: &str,
sig_hex: &str,
) {
let signer = SchnorrSigner::from_hex(sk_hex).unwrap();
assert_eq!(
signer.xonly_public_key_hex(),
pk_hex,
"vector {index}: x-only pubkey must match CSV",
);
let produced = sign_primitive(sk_hex, aux_hex, msg_hex);
assert_eq!(
hex::encode(produced),
sig_hex,
"vector {index}: BIP-340 signature bytes must match CSV",
);
let msg = hex::decode(msg_hex).unwrap();
signer.verify(&msg, &produced).unwrap();
let mut tampered = produced;
tampered[0] ^= 0x01;
assert!(signer.verify(&msg, &tampered).is_err());
}
#[test]
fn index_0_minimum_scalar_matches_csv() {
check_sign_and_verify(
0,
"0000000000000000000000000000000000000000000000000000000000000003",
"f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
"e907831f80848d1069a5371b402410364bdf1c5f8307b0084c55f1ce2dca821525f66a4a85ea8b71e482a74f382d2ce5ebeee8fdb2172f477df4900d310536c0",
);
}
#[test]
fn index_1_generator_scalar_matches_csv() {
check_sign_and_verify(
1,
"b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfef",
"dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
"0000000000000000000000000000000000000000000000000000000000000001",
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
"6896bd60eeae296db48a229ff71dfe071bde413e6d43f917dc8dcf8c78de33418906d11ac976abccb20b091292bff4ea897efcb639ea871cfa95f6de339e4b0a",
);
}
#[test]
fn index_2_negation_path_matches_csv() {
check_sign_and_verify(
2,
"c90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b14e5c9",
"dd308afec5777e13121fa72b9cc1b7cc0139715309b086c960e18fd969774eb8",
"c87aa53824b4d7ae2eb035a2b5bbbccc080e76cdc6d1692c4b0b62d798e6d906",
"7e2d58d8b3bcdf1abadec7829054f90dda9805aab56c77333024b9d0a508b75c",
"5831aaeed7b44bb74e5eab94ba9d4294c49bcf2a60728d8b4c200f50dd313c1bab745879a5ad954a72c45a91c3a51d3c7adea98d82f8481e0e1e03674a6f3fb7",
);
}
#[test]
fn index_3_all_ones_modulo_edge_matches_csv() {
check_sign_and_verify(
3,
"0b432b2677937381aef05bb02a66ecd012773062cf3fa2549e44f58ed2401710",
"25d1dff95105f5253c4022f628a996ad3a0d95fbf21d468a1b33f8c160d8f517",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"7eb0509757e246f19449885651611cb965ecc1a187dd51b64fda1edc9637d5ec97582b9cb13db3933705b32ba982af5af25fd78881ebb32771fc5922efc66ea3",
);
}
#[test]
fn index_5_pubkey_not_on_curve_is_rejected() {
let pubkey_hex = "eefdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34";
let pk_bytes = hex::decode(pubkey_hex).unwrap();
assert!(
VerifyingKey::from_bytes(&pk_bytes).is_err(),
"x-only pubkey off-curve must be rejected by k256",
);
}
#[test]
fn index_6_r_has_odd_y_is_rejected() {
let pubkey_hex = "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659";
let msg_hex = "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89";
let sig_hex = "fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975563cc27944640ac607cd107ae10923d9ef7a73c643e166be5ebeafa34b1ac553e2";
let pk_bytes = hex::decode(pubkey_hex).unwrap();
let vk = VerifyingKey::from_bytes(&pk_bytes).unwrap();
let sig = K256Sig::try_from(hex::decode(sig_hex).unwrap().as_slice()).unwrap();
let msg = hex::decode(msg_hex).unwrap();
assert!(
vk.verify_raw(&msg, &sig).is_err(),
"R with odd y must be rejected by BIP-340 verifier",
);
}
}
#[cfg(feature = "secp256k1")]
mod secp256k1_rfc6979 {
use crate::{Secp256k1Signer, SignOutput};
const KEY_HEX: &str = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318";
const DIGEST_HEX: &str = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20";
const SIG_HEX: &str = "68597f9553ac0acc453b5a75af2c731e3ca14dbfeae2231123fd202765b12738247bc920ef3e3ceebbc865651f98dc26a25a0d63240c5da091863fe0296e389b00";
const SIG_DER_HEX: &str = "3044022068597f9553ac0acc453b5a75af2c731e3ca14dbfeae2231123fd202765b127380220247bc920ef3e3ceebbc865651f98dc26a25a0d63240c5da091863fe0296e389b";
#[test]
fn deterministic_sig_bytes_match_noble_curves_kat() {
let s = Secp256k1Signer::from_hex(KEY_HEX).unwrap();
let digest: [u8; 32] = hex::decode(DIGEST_HEX).unwrap().try_into().unwrap();
let out = s.sign_prehash_recoverable(&digest).unwrap();
assert_eq!(out.to_hex(), SIG_HEX);
let der = s.sign_prehash_der(&digest).unwrap();
assert_eq!(der.to_hex(), SIG_DER_HEX);
let mut compact = [0u8; 64];
compact.copy_from_slice(&out.to_bytes()[..64]);
s.verify_prehash(&digest, &compact).unwrap();
s.verify_prehash_recoverable(&digest, out.to_bytes().as_slice().try_into().unwrap())
.unwrap();
s.verify_prehash_any(&digest, &out.to_bytes()).unwrap();
let SignOutput::EcdsaDer(ref der_bytes) = der else {
panic!("expected EcdsaDer");
};
s.verify_prehash_der(&digest, der_bytes).unwrap();
}
}