266 lines
9.9 KiB
Rust
266 lines
9.9 KiB
Rust
// 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_zebra as ed25519_zebra_zip215;
|
|
|
|
use core::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_zebra_zip215::{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,
|
|
],
|
|
];
|