// functions are used in small_order but not recognized as such? #![allow(dead_code)] use color_eyre::{eyre::eyre, Report}; use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; use ed25519_consensus; use std::convert::TryFrom; pub struct TestCase { pub vk_bytes: [u8; 32], pub sig_bytes: [u8; 64], pub valid_legacy: bool, pub valid_zip215: bool, } impl core::fmt::Debug for TestCase { fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { fmt.debug_struct("TestCase") .field("vk_bytes", &hex::encode(&self.vk_bytes[..])) .field("sig_bytes", &hex::encode(&self.sig_bytes[..])) .field("valid_legacy", &self.valid_legacy) .field("valid_zip215", &self.valid_zip215) .finish() } } impl TestCase { pub fn check(&self) -> Result<(), Report> { match (self.valid_legacy, self.check_legacy()) { (false, Err(_)) => Ok(()), (true, Ok(())) => Ok(()), (false, Ok(())) => Err(eyre!( "legacy-invalid signature case validated under legacy rules" )), (true, Err(e)) => { Err(e.wrap_err("legacy-valid signature case was rejected under legacy rules")) } }?; match (self.valid_zip215, self.check_zip215()) { (false, Err(_)) => Ok(()), (true, Ok(())) => Ok(()), (false, Ok(())) => Err(eyre!( "zip215-invalid signature case validated under zip215 rules" )), (true, Err(e)) => { Err(e.wrap_err("zip215-valid signature case was rejected under zip215 rules")) } } } fn check_legacy(&self) -> Result<(), Report> { use ed25519_zebra_legacy::{Signature, VerificationKey}; let sig = Signature::from(self.sig_bytes); VerificationKey::try_from(self.vk_bytes).and_then(|vk| vk.verify(&sig, b"Zcash"))?; Ok(()) } fn check_zip215(&self) -> Result<(), Report> { use ed25519_consensus::{Signature, VerificationKey}; let sig = Signature::from(self.sig_bytes); VerificationKey::try_from(self.vk_bytes).and_then(|vk| vk.verify(&sig, b"Zcash"))?; Ok(()) } } pub fn non_canonical_field_encodings() -> Vec<[u8; 32]> { // There are 19 finite field elements which can be represented // non-canonically as x + p with x + p fitting in 255 bits: let mut bytes = [ 237, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, ]; let mut encodings = Vec::new(); for i in 0..19u8 { bytes[0] = 237 + i; encodings.push(bytes); } encodings } // Compute all 25 non-canonical point encodings. The first 5 are low order. pub fn non_canonical_point_encodings() -> Vec<[u8; 32]> { // Points are encoded by the y-coordinate and a bit indicating the // sign of the x-coordinate. There are two ways to construct a // non-canonical point encoding: // // (1) by using a non-canonical encoding of y (cf RFC8032§5.1.3.1) // (2) by selecting y so that both sign choices give the same x. // // Condition (1) can occur only for 19 field elements that can be encoded // non-canonically as y + p with y + p fitting in 255 bits. // // Condition (2) occurs if and only if x = -x, i.e., x = 0. // The curve equation is ax^2 + y^2 = 1 + dx^2 + y^2 so x = 0 => y^2 = 1. // This means y = 1 or y = -1. // // When y = -1, y can only be canonically encoded, so the encodings of (0,-1) are: // * enc(-1) || 0 [canonical] // * enc(-1) || 1 [non-canonical] // // When y = 1, y can be non-canonically encoded, so the encodings of (0,1) are: // * enc(1) || 0 [canonical] // * enc(1) || 1 [non-canonical] // * enc(2^255 - 18) || 0 [non-canonical] // * enc(2^255 - 18) || 1 [non-canonical] // // We pick up the latter two in generation of non-canonically encoded field elements, // and construct the first two explicitly. // // RFC8032§5.1.3.4 requires implementations to perform a field element equality check // on the x value computed inside the decompression routine and abort if x = 0 and // the sign bit was set. However, no implementations do this, and any implementation // that did would then be subtly incompatible with others in a new and different way. // // (This taxonomy was created with pointers from Sean Bowe and NCC Group). let mut encodings = Vec::new(); // Canonical y with non-canonical sign bits. let y1_noncanonical_sign_bit = [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, ]; encodings.push(y1_noncanonical_sign_bit); let ym1_noncanonical_sign_bit = [ 236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, ]; encodings.push(ym1_noncanonical_sign_bit); // Run through non-canonical field elements. // Not all field elements are x-coordinates of curve points, so check: for mut x in non_canonical_field_encodings().into_iter() { if CompressedEdwardsY(x).decompress().is_some() { encodings.push(x); } x[31] |= 128; if CompressedEdwardsY(x).decompress().is_some() { encodings.push(x); } } // Check that all of the non-canonical points are really non-canonical for &e in &encodings { assert_ne!( e, CompressedEdwardsY(e) .decompress() .unwrap() .compress() .to_bytes() ); } encodings } // Running this reveals that only the first 6 entries on the list have low order. #[test] fn print_non_canonical_points() { for encoding in non_canonical_point_encodings().into_iter() { let point = CompressedEdwardsY(encoding).decompress().unwrap(); println!( "encoding {} has order {}", hex::encode(&encoding[..]), order(point) ); } } pub fn order(point: EdwardsPoint) -> &'static str { use curve25519_dalek::traits::IsIdentity; if point.is_small_order() { let point2 = point + point; let point4 = point2 + point2; if point.is_identity() { "1" } else if point2.is_identity() { "2" } else if point4.is_identity() { "4" } else { "8" } } else { if point.is_torsion_free() { "p" } else { "8p" } } } #[test] fn find_valid_excluded_encodings() { for (i, encoding) in EXCLUDED_POINT_ENCODINGS.iter().enumerate() { if let Some(point) = CompressedEdwardsY(*encoding).decompress() { println!("index {} is valid point of order {}", i, order(point)); } else { println!("index {} is not a valid encoding", i); } } } /// These point encodings were specifically blacklisted by libsodium 1.0.15, in /// an apparent (and unsuccessful) attempt to exclude points of low order. /// /// To maintain exact compatibility with this version of libsodium, we encode /// them here, following the Zcash protocol specification. pub static EXCLUDED_POINT_ENCODINGS: [[u8; 32]; 11] = [ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], [ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ], [ 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05, ], [ 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a, ], [ 0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85, ], [ 0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa, ], [ 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, ], [ 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, ], [ 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, ], [ 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ], [ 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ], ];