diff --git a/rust-arkworks/src/error.rs b/rust-arkworks/src/error.rs index 85a7fbe..ac7546f 100644 --- a/rust-arkworks/src/error.rs +++ b/rust-arkworks/src/error.rs @@ -1,11 +1,26 @@ -use thiserror::Error; +// Legacy definitions for errors which will be gone with arkworks upgrade to `>=0.4.0`. +// `use ark_ec::hashing::HashToCurveError;` + +// use thiserror::Error; /// This is an error that could occur when running a cryptograhic primitive -#[derive(Error, Debug, PartialEq)] -pub enum CryptoError { - #[error("Cannot hash to curve")] - CannotHashToCurve, +// #[derive(Error, Debug, PartialEq)] +// pub enum CryptoError { +// #[error("Cannot hash to curve")] +// CannotHashToCurve, + +// #[error("Cannot encode a point not on the curve")] +// PointNotOnCurve, +// } - #[error("Cannot encode a point not on the curve")] - PointNotOnCurve, +// Let's outline what errors will be in `~0.4.0` +#[derive(Debug, Clone)] +pub enum HashToCurveError { + UnsupportedCurveError(String), + MapToCurveError(String), + /* let's add two more items to absorb everything + in `crate::hash_to_curve` which is + subject to deprecation */ + Legacy, + ReferenceTryAndIncrement, } diff --git a/rust-arkworks/src/hash_to_curve.rs b/rust-arkworks/src/hash_to_curve.rs index 6802654..8b19697 100644 --- a/rust-arkworks/src/hash_to_curve.rs +++ b/rust-arkworks/src/hash_to_curve.rs @@ -1,21 +1,19 @@ -use crate::error::CryptoError; +use crate::error::HashToCurveError; use ark_ec::short_weierstrass_jacobian::GroupAffine; use ark_ec::{AffineCurve, ProjectiveCurve}; use ark_ff::FromBytes; use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest}; use elliptic_curve::sec1::ToEncodedPoint; -use k256::sha2::Sha256; -use k256::AffinePoint; -use k256::{ProjectivePoint, Secp256k1}; +// TODO why not ark libs for these? oO +use k256::{sha2::Sha256, AffinePoint, ProjectivePoint, Secp256k1}; use secp256k1::Sec1EncodePoint; use tiny_keccak::{Hasher, Shake, Xof}; pub fn hash_to_curve( msg: &[u8], pk: &GroupAffine

, -) -> GroupAffine

{ - let pk_encoded = pk.to_encoded_point(true); - let b = hex::decode(pk_encoded).unwrap(); +) -> Result, HashToCurveError> { + let b = hex::decode(&pk.to_encoded_point(true)).expect(super::EXPECT_MSG_DECODE); let x = [msg, b.as_slice()]; let x = x.concat().clone(); let x = x.as_slice(); @@ -24,7 +22,7 @@ pub fn hash_to_curve( &[x], b"QUUX-V01-CS02-with-secp256k1_XMD:SHA-256_SSWU_RO_", ) - .unwrap(); + .map_err(|_| HashToCurveError::Legacy)?; let pt_affine = pt.to_affine(); @@ -33,13 +31,13 @@ pub fn hash_to_curve( pub fn k256_affine_to_arkworks_secp256k1_affine( k_pt: AffinePoint, -) -> GroupAffine

{ +) -> Result, HashToCurveError> { let encoded_pt = k_pt.to_encoded_point(false); let num_field_bytes = 40; // extract k_pt.x - let k_pt_x_bytes = encoded_pt.x().unwrap(); + let k_pt_x_bytes = encoded_pt.x().ok_or(HashToCurveError::Legacy)?; // pad x bytes let mut k_pt_x_bytes_vec = vec![0u8; num_field_bytes]; @@ -50,10 +48,10 @@ pub fn k256_affine_to_arkworks_secp256k1_affine( ); } let reader = std::io::BufReader::new(k_pt_x_bytes_vec.as_slice()); - let g_x = P::BaseField::read(reader).unwrap(); + let g_x = P::BaseField::read(reader).map_err(|_| HashToCurveError::Legacy)?; // extract k_pt.y - let k_pt_y_bytes = encoded_pt.y().unwrap(); + let k_pt_y_bytes = encoded_pt.y().ok_or(HashToCurveError::Legacy)?; // pad y bytes let mut k_pt_y_bytes_vec = vec![0u8; num_field_bytes]; @@ -65,13 +63,13 @@ pub fn k256_affine_to_arkworks_secp256k1_affine( } let reader = std::io::BufReader::new(k_pt_y_bytes_vec.as_slice()); - let g_y = P::BaseField::read(reader).unwrap(); + let g_y = P::BaseField::read(reader).map_err(|_| HashToCurveError::Legacy)?; - GroupAffine::

::new(g_x, g_y, false) + Ok(GroupAffine::

::new(g_x, g_y, false)) } /// Kobi's hash_to_curve function, here for reference only -pub fn _try_and_increment(msg: &[u8]) -> Result { +pub fn _try_and_increment(msg: &[u8]) -> Result { for nonce in 0u8..=255 { let mut h = Shake::v128(); h.update(&[nonce]); @@ -85,5 +83,5 @@ pub fn _try_and_increment(msg: &[u8]) -> Result { - _group: PhantomData, - _field: PhantomData, - _parameters: PhantomData

, - _message_lifetime: PhantomData<&'a ()>, - } +const EXPECT_MSG_DECODE: &str = "the value decoded have been generated by a function which is improbable to output a malformed hexstring (still a place for refactoring)"; - pub fn affine_to_bytes(point: &GroupAffine

) -> Vec { - let encoded = point.to_encoded_point(true); - let b = hex::decode(encoded).unwrap(); - b.to_vec() - } +pub enum PlumeVersion { + V1, + V2, +} - fn compute_h<'a, C: ProjectiveCurve, Fq: PrimeField, P: SWModelParameters>( - pk: &GroupAffine

, - message: &'a [u8], - ) -> Result, CryptoError> { - //let pk_affine_bytes_vec = affine_to_bytes::

(pk); - //let m_pk = [message, pk_affine_bytes_vec.as_slice()].concat(); - //hash_to_curve::try_and_increment::(m_pk.as_slice()) - Ok(hash_to_curve::hash_to_curve::(message, pk)) - } +pub fn affine_to_bytes(point: &GroupAffine

) -> Vec { + hex::decode(point.to_encoded_point(true)) + .expect(EXPECT_MSG_DECODE) + .to_vec() +} - fn compute_c_v1( - g_point: &GroupAffine

, - pk: &GroupAffine

, - hashed_to_curve: &GroupAffine

, - nullifier: &GroupAffine

, - r_point: &GroupAffine

, - hashed_to_curve_r: &GroupAffine

, - ) -> Output { - // Compute c = sha512([g, pk, h, nul, g^r, z]) - let c_preimage_vec = [ - affine_to_bytes::

(g_point), - affine_to_bytes::

(pk), - affine_to_bytes::

(hashed_to_curve), - affine_to_bytes::

(nullifier), - affine_to_bytes::

(r_point), - affine_to_bytes::

(hashed_to_curve_r), - ] - .concat(); - - Sha256::digest(c_preimage_vec.as_slice()) - } +fn compute_h<'a, C: ProjectiveCurve, Fq: PrimeField, P: SWModelParameters>( + pk: &GroupAffine

, + message: &'a [u8], +) -> Result, HashToCurveError> { + //let pk_affine_bytes_vec = affine_to_bytes::

(pk); + //let m_pk = [message, pk_affine_bytes_vec.as_slice()].concat(); + //hash_to_curve::try_and_increment::(m_pk.as_slice()) + hash_to_curve::(message, pk) +} - fn compute_c_v2( - nullifier: &GroupAffine

, - r_point: &GroupAffine

, - hashed_to_curve_r: &GroupAffine

, - ) -> Output { - // Compute c = sha512([nul, g^r, z]) - let nul_bytes = affine_to_bytes::

(nullifier); - let g_r_bytes = affine_to_bytes::

(r_point); - let z_bytes = affine_to_bytes::

(hashed_to_curve_r); +fn compute_c_v1( + g_point: &GroupAffine

, + pk: &GroupAffine

, + hashed_to_curve: &GroupAffine

, + nullifier: &GroupAffine

, + r_point: &GroupAffine

, + hashed_to_curve_r: &GroupAffine

, +) -> Output { + // Compute c = sha512([g, pk, h, nul, g^r, z]) + let c_preimage_vec = [ + affine_to_bytes::

(g_point), + affine_to_bytes::

(pk), + affine_to_bytes::

(hashed_to_curve), + affine_to_bytes::

(nullifier), + affine_to_bytes::

(r_point), + affine_to_bytes::

(hashed_to_curve_r), + ] + .concat(); + + Sha256::digest(c_preimage_vec.as_slice()) +} - let c_preimage_vec = [nul_bytes, g_r_bytes, z_bytes].concat(); +fn compute_c_v2( + nullifier: &GroupAffine

, + r_point: &GroupAffine

, + hashed_to_curve_r: &GroupAffine

, +) -> Output { + // Compute c = sha512([nul, g^r, z]) + let nul_bytes = affine_to_bytes::

(nullifier); + let g_r_bytes = affine_to_bytes::

(r_point); + let z_bytes = affine_to_bytes::

(hashed_to_curve_r); - Sha256::digest(c_preimage_vec.as_slice()) - } + let c_preimage_vec = [nul_bytes, g_r_bytes, z_bytes].concat(); - pub trait VerifiableUnpredictableFunction { - type Message: ToBytes; - type Parameters: CanonicalSerialize + CanonicalDeserialize; - type PublicKey: CanonicalSerialize + CanonicalDeserialize; - type SecretKey: CanonicalSerialize + CanonicalDeserialize; - type Signature: CanonicalSerialize + CanonicalDeserialize; - - /// Generate a public key and a private key. - fn keygen( - pp: &Self::Parameters, - rng: &mut R, - ) -> Result<(Self::PublicKey, Self::SecretKey), CryptoError>; - - /// Sign a message. - fn sign( - pp: &Self::Parameters, - rng: &mut R, - keypair: (&Self::PublicKey, &Self::SecretKey), - message: Self::Message, - version: PlumeVersion, - ) -> Result; - - /// Sign a message using an specified r value - fn sign_with_r( - pp: &Self::Parameters, - keypair: (&Self::PublicKey, &Self::SecretKey), - message: Self::Message, - r_scalar: Self::SecretKey, - version: PlumeVersion, - ) -> Result; - - fn verify_non_zk( - pp: &Self::Parameters, - pk: &Self::PublicKey, - sig: &Self::Signature, - message: Self::Message, - version: PlumeVersion, - ) -> Result; - } + Sha256::digest(c_preimage_vec.as_slice()) +} - #[derive( - Copy, - Clone, - ark_serialize_derive::CanonicalSerialize, - ark_serialize_derive::CanonicalDeserialize, - )] - pub struct Parameters { - pub g_point: GroupAffine

, - } +#[derive( + Copy, + Clone, + ark_serialize_derive::CanonicalSerialize, + ark_serialize_derive::CanonicalDeserialize, +)] +pub struct Parameters { + pub g_point: GroupAffine

, +} - #[derive( - Copy, - Clone, - ark_serialize_derive::CanonicalSerialize, - ark_serialize_derive::CanonicalDeserialize, - )] - pub struct Signature { - pub hashed_to_curve_r: GroupAffine

, - pub r_point: GroupAffine

, - pub s: P::ScalarField, - pub c: P::ScalarField, - pub nullifier: GroupAffine

, +#[derive( + Copy, + Clone, + ark_serialize_derive::CanonicalSerialize, + ark_serialize_derive::CanonicalDeserialize, +)] +pub struct PlumeSignature { + pub hashed_to_curve_r: GroupAffine

, + pub r_point: GroupAffine

, + pub s: P::ScalarField, + pub c: P::ScalarField, + pub nullifier: GroupAffine

, +} + +// These aliases should be gone in #88 . If they won't TODO pay attention to the warning about `trait` boundaries being not checked for aliases +// also not enforcing trait bounds can impact PublicKey -- it's better to find appropriate upstream type +type Message<'a> = &'a [u8]; +type PublicKey = GroupAffine

; +type SecretKeyMaterial = P::ScalarField; + +impl PlumeSignature

{ + /// Generate the public key and a private key. + fn keygen(pp: &Parameters

, rng: &mut impl Rng) -> (PublicKey

, SecretKeyMaterial

) { + let secret_key = SecretKeyMaterial::

::rand(rng); + let public_key = pp.g_point.mul(secret_key).into(); + (public_key, secret_key) } - impl<'a, C: ProjectiveCurve, Fq: PrimeField, P: SWModelParameters> - VerifiableUnpredictableFunction for DeterministicNullifierSignatureScheme<'a, C, Fq, P> - { - type Message = &'a [u8]; - type Parameters = Parameters

; - type PublicKey = GroupAffine

; - type SecretKey = P::ScalarField; - type Signature = Signature

; - - fn keygen( - pp: &Self::Parameters, - rng: &mut R, - ) -> Result<(Self::PublicKey, Self::SecretKey), CryptoError> { - let secret_key = Self::SecretKey::rand(rng).into(); - let public_key = pp.g_point.mul(secret_key).into(); - Ok((public_key, secret_key)) - } + /// Sign a message using a specified r value + fn sign_with_r( + pp: &Parameters

, + keypair: (&PublicKey

, &SecretKeyMaterial

), + message: Message, + r_scalar: P::ScalarField, + version: PlumeVersion, + ) -> Result { + let g_point = pp.g_point; + let r_point = g_point.mul(r_scalar).into_affine(); - fn sign_with_r( - pp: &Self::Parameters, - keypair: (&Self::PublicKey, &Self::SecretKey), - message: Self::Message, - r_scalar: P::ScalarField, - version: PlumeVersion, - ) -> Result { - let g_point = pp.g_point; - let r_point = g_point.mul(r_scalar).into_affine(); - - // Compute h = htc([m, pk]) - let hashed_to_curve = compute_h::(&keypair.0, &message).unwrap(); - - // Compute z = h^r - let hashed_to_curve_r = hashed_to_curve.mul(r_scalar).into_affine(); - - // Compute nul = h^sk - let nullifier = hashed_to_curve.mul(*keypair.1).into_affine(); - - // Compute c = sha512([g, pk, h, nul, g^r, z]) - let c = match version { - PlumeVersion::V1 => compute_c_v1::

( - &g_point, - keypair.0, - &hashed_to_curve, - &nullifier, - &r_point, - &hashed_to_curve_r, - ), - PlumeVersion::V2 => compute_c_v2(&nullifier, &r_point, &hashed_to_curve_r), - }; - let c_scalar = P::ScalarField::from_be_bytes_mod_order(c.as_ref()); - // Compute s = r + sk ⋅ c - let sk_c = keypair.1.into_repr().into() * c_scalar.into_repr().into(); - let s = r_scalar.into_repr().into() + sk_c; - - let s_scalar = P::ScalarField::from(s); - - let signature = Signature { - hashed_to_curve_r, - s: s_scalar, - r_point, - c: c_scalar, - nullifier, - }; - Ok(signature) - } + // Compute h = htc([m, pk]) + let hashed_to_curve = + compute_h::(&keypair.0, &message)?; - fn sign( - pp: &Self::Parameters, - rng: &mut R, - keypair: (&Self::PublicKey, &Self::SecretKey), - message: Self::Message, - version: PlumeVersion, - ) -> Result { - // Pick a random r from Fp - let r_scalar: P::ScalarField = Self::SecretKey::rand(rng).into(); - - Self::sign_with_r(pp, keypair, message, r_scalar, version) - } + // Compute z = h^r + let hashed_to_curve_r = hashed_to_curve.mul(r_scalar).into_affine(); - fn verify_non_zk( - pp: &Self::Parameters, - pk: &Self::PublicKey, - sig: &Self::Signature, - message: Self::Message, - version: PlumeVersion, - ) -> Result { - // Compute h = htc([m, pk]) - let hashed_to_curve = compute_h::(pk, message).unwrap(); - - // TODO [replace SHA-512](https://github.com/plume-sig/zk-nullifier-sig/issues/39#issuecomment-1732497672) - // Compute c' = sha512([g, pk, h, nul, g^r, z]) for v1 - // c' = sha512([nul, g^r, z]) for v2 - let c = match version { - PlumeVersion::V1 => compute_c_v1::

( - &pp.g_point, - pk, - &hashed_to_curve, - &sig.nullifier, - &sig.r_point, - &sig.hashed_to_curve_r, - ), - PlumeVersion::V2 => { - compute_c_v2(&sig.nullifier, &sig.r_point, &sig.hashed_to_curve_r) - } - }; - let c_scalar = P::ScalarField::from_be_bytes_mod_order(c.as_ref()); - - // Reject if g^s ⋅ pk^{-c} != g^r - let g_s = pp.g_point.mul(sig.s); - let pk_c = pk.mul(sig.c); - let g_s_pk_c = g_s - pk_c; - - if sig.r_point != g_s_pk_c { - return Ok(false); - } + // Compute nul = h^sk + let nullifier = hashed_to_curve.mul(*keypair.1).into_affine(); - // Reject if h^s ⋅ nul^{-c} = z - let h_s = hashed_to_curve.mul(sig.s); - let nul_c = sig.nullifier.mul(sig.c); - let h_s_nul_c = h_s - nul_c; + // Compute c = sha512([g, pk, h, nul, g^r, z]) + let c = match version { + PlumeVersion::V1 => compute_c_v1::

( + &g_point, + keypair.0, + &hashed_to_curve, + &nullifier, + &r_point, + &hashed_to_curve_r, + ), + PlumeVersion::V2 => compute_c_v2(&nullifier, &r_point, &hashed_to_curve_r), + }; + let c_scalar = P::ScalarField::from_be_bytes_mod_order(c.as_ref()); + // Compute s = r + sk ⋅ c + let sk_c = keypair.1.into_repr().into() * c_scalar.into_repr().into(); + let s = r_scalar.into_repr().into() + sk_c; + + let s_scalar = P::ScalarField::from(s); + + let signature = PlumeSignature { + hashed_to_curve_r, + s: s_scalar, + r_point, + c: c_scalar, + nullifier, + }; + Ok(signature) + } - if sig.hashed_to_curve_r != h_s_nul_c { - return Ok(false); - } + /// Sign a message. + fn sign( + pp: &Parameters

, + rng: &mut impl Rng, + keypair: (&PublicKey

, &SecretKeyMaterial

), + message: Message, + version: PlumeVersion, + ) -> Result { + // Pick a random r from Fp + let r_scalar = P::ScalarField::rand(rng); + + Self::sign_with_r(pp, keypair, message, r_scalar, version) + } - // Reject if c != c' - if c_scalar != sig.c { - return Ok(false); + fn verify_non_zk( + self, + pp: &Parameters

, + pk: &PublicKey

, + message: Message, + version: PlumeVersion, + ) -> Result { + // Compute h = htc([m, pk]) + let hashed_to_curve = + compute_h::(pk, message)?; + + // TODO [replace SHA-512](https://github.com/plume-sig/zk-nullifier-sig/issues/39#issuecomment-1732497672) + // Compute c' = sha512([g, pk, h, nul, g^r, z]) for v1 + // c' = sha512([nul, g^r, z]) for v2 + let c = match version { + PlumeVersion::V1 => compute_c_v1::

( + &pp.g_point, + pk, + &hashed_to_curve, + &self.nullifier, + &self.r_point, + &self.hashed_to_curve_r, + ), + PlumeVersion::V2 => { + compute_c_v2(&self.nullifier, &self.r_point, &self.hashed_to_curve_r) } + }; + let c_scalar = P::ScalarField::from_be_bytes_mod_order(c.as_ref()); + + // Reject if g^s ⋅ pk^{-c} != g^r + let g_s = pp.g_point.mul(self.s); + let pk_c = pk.mul(self.c); + let g_s_pk_c = g_s - pk_c; + + if self.r_point != g_s_pk_c { + return Ok(false); + } - Ok(true) + // Reject if h^s ⋅ nul^{-c} = z + let h_s = hashed_to_curve.mul(self.s); + let nul_c = self.nullifier.mul(self.c); + let h_s_nul_c = h_s - nul_c; + + if self.hashed_to_curve_r != h_s_nul_c { + return Ok(false); + } + + // Reject if c != c' + if c_scalar != self.c { + return Ok(false); } + + Ok(true) } } diff --git a/rust-arkworks/src/tests.rs b/rust-arkworks/src/tests.rs index d0eba46..1f54c89 100644 --- a/rust-arkworks/src/tests.rs +++ b/rust-arkworks/src/tests.rs @@ -1,7 +1,5 @@ use crate::hash_to_curve::{hash_to_curve, k256_affine_to_arkworks_secp256k1_affine}; -use crate::sig::DeterministicNullifierSignatureScheme; -use crate::sig::PlumeVersion; -use crate::sig::VerifiableUnpredictableFunction; +use crate::{PlumeSignature, PlumeVersion}; use ark_ec::models::short_weierstrass_jacobian::GroupAffine; use ark_ec::{AffineCurve, ProjectiveCurve}; use ark_ff::biginteger; @@ -13,7 +11,7 @@ use secp256k1::curves::Affine; use secp256k1::curves::Secp256k1Parameters; use secp256k1::fields::Fq; -type Parameters = crate::sig::Parameters; +type Parameters = crate::Parameters; fn test_template() -> (ThreadRng, Affine) { let rng = thread_rng(); @@ -22,9 +20,6 @@ fn test_template() -> (ThreadRng, Affine) { (rng, g) } -type Scheme<'a> = - DeterministicNullifierSignatureScheme<'a, secp256k1::Projective, Fq, Secp256k1Parameters>; - #[test] pub fn test_k256_affine_to_arkworks_secp256k1_affine() { for i in 1..50 { @@ -41,7 +36,7 @@ pub fn test_k256_affine_to_arkworks_secp256k1_affine() { k256_affine_to_arkworks_secp256k1_affine::(k256_pt.to_affine()); // The points should match - assert_eq!(ark_pt.into_affine(), converted_pt); + assert_eq!(ark_pt.into_affine(), converted_pt.unwrap()); } } @@ -84,7 +79,7 @@ pub fn test_keygen() { let (mut rng, g) = test_template(); let pp = Parameters { g_point: g }; - let (pk, sk) = Scheme::keygen(&pp, &mut rng).unwrap(); + let (pk, sk) = PlumeSignature::keygen(&pp, &mut rng); let expected_pk = g.mul(sk); assert_eq!(pk, expected_pk); @@ -96,9 +91,9 @@ pub fn test_sign_and_verify() { let pp = Parameters { g_point: g }; let message = b"Message"; - let keypair = Scheme::keygen(&pp, &mut rng).unwrap(); + let keypair = PlumeSignature::keygen(&pp, &mut rng); - let sig = Scheme::sign( + let sig = PlumeSignature::sign( &pp, &mut rng, (&keypair.0, &keypair.1), @@ -107,10 +102,10 @@ pub fn test_sign_and_verify() { ) .unwrap(); - let is_valid = Scheme::verify_non_zk(&pp, &keypair.0, &sig, message, PlumeVersion::V1); + let is_valid = sig.verify_non_zk(&pp, &keypair.0, message, PlumeVersion::V1); assert!(is_valid.unwrap()); - let sig = Scheme::sign( + let sig = PlumeSignature::sign( &pp, &mut rng, (&keypair.0, &keypair.1), @@ -119,7 +114,7 @@ pub fn test_sign_and_verify() { ) .unwrap(); - let is_valid = Scheme::verify_non_zk(&pp, &keypair.0, &sig, message, PlumeVersion::V2); + let is_valid = sig.verify_non_zk(&pp, &keypair.0, message, PlumeVersion::V2); assert!(is_valid.unwrap()); } @@ -132,8 +127,7 @@ pub fn compute_h() -> GroupAffine { let pk_projective = g.mul(sk); let pk = GroupAffine::::from(pk_projective); - let h = hash_to_curve::(message, &pk); - h + hash_to_curve::(message, &pk).unwrap() } #[test] @@ -236,7 +230,8 @@ pub fn test_against_zk_nullifier_sig_c_and_s() { let keypair = (pk, sk); let sig = - Scheme::sign_with_r(&pp, (&keypair.0, &keypair.1), message, r, PlumeVersion::V1).unwrap(); + PlumeSignature::sign_with_r(&pp, (&keypair.0, &keypair.1), message, r, PlumeVersion::V1) + .unwrap(); assert_eq!( coord_to_hex(sig.c.into()), @@ -248,7 +243,8 @@ pub fn test_against_zk_nullifier_sig_c_and_s() { ); let sig = - Scheme::sign_with_r(&pp, (&keypair.0, &keypair.1), message, r, PlumeVersion::V2).unwrap(); + PlumeSignature::sign_with_r(&pp, (&keypair.0, &keypair.1), message, r, PlumeVersion::V2) + .unwrap(); assert_eq!( coord_to_hex(sig.c.into()),