anza-cryptography-source/syscall/bls12-381-syscall/src/decompression.rs
Sam Kim 09198923bb
Add workspace.package information and do minor clean-up (#14)
* remove README.md in the syscall directory

* use workspace dependency in `bls12-381`

* add `workspace.package` information

* use 2021 edition for bls12-381

* inherit workspace.package for `ed25519-pokos`

* cargo fmt
2026-04-26 10:11:05 +09:00

267 lines
8.4 KiB
Rust

use {
crate::{
encoding::{
swap_fq_endianness, swap_g2_c0_c1, Endianness, PodG1Compressed, PodG1Point,
PodG2Compressed, PodG2Point,
},
Version,
},
blstrs::{G1Affine, G2Affine},
};
/// Decompresses a compressed G1 point.
pub fn bls12_381_g1_decompress(
_version: Version,
input: &PodG1Compressed,
endianness: Endianness,
) -> Option<PodG1Point> {
let p1 = match endianness {
// `G2Affine::from_compressed_unchecked` performs field and on-curve checks
Endianness::BE => G1Affine::from_compressed_unchecked(&input.0).into_option()?,
Endianness::LE => {
let mut bytes = input.0;
swap_fq_endianness(&mut bytes);
// After reversal, the flag byte (originally at end) is now at index 0.
// This matches the [Zcash BE format][zcash-be-format] expected by
// `G1Affine::from_compressed_unchecked`.
//
// [zcash-be-format]: https://github.com/zkcrypto/pairing/blob/34aa52b0f7bef705917252ea63e5a13fa01af551/src/bls12_381/README.md#serialization
G1Affine::from_compressed_unchecked(&bytes).into_option()?
}
};
// field and on-curve checks are already performed, so just check subgroup
if !bool::from(p1.is_torsion_free()) {
return None;
}
let mut result = PodG1Point(p1.to_uncompressed());
if matches!(endianness, Endianness::LE) {
swap_fq_endianness(&mut result.0);
}
Some(result)
}
/// Decompresses a compressed G2 point.
pub fn bls12_381_g2_decompress(
_version: Version,
input: &PodG2Compressed,
endianness: Endianness,
) -> Option<PodG2Point> {
let p2 = match endianness {
// `G1Affine::from_compressed_unchecked` performs field and on-curve checks
Endianness::BE => G2Affine::from_compressed_unchecked(&input.0).into_option()?,
Endianness::LE => {
let mut bytes = input.0;
swap_fq_endianness(&mut bytes);
swap_g2_c0_c1(&mut bytes); // Swap c0/c1 for G2
// After reversal, the flag byte (originally at end) is now at index 0.
// This matches the [Zcash BE format][zcash-be-format] expected by
// `G2Affine::from_compressed_unchecked`.
//
// [zcash-be-format]: https://github.com/zkcrypto/pairing/blob/34aa52b0f7bef705917252ea63e5a13fa01af551/src/bls12_381/README.md#serialization
G2Affine::from_compressed_unchecked(&bytes).into_option()?
}
};
// field and on-curve checks are already performed, so just check subgroup
if !bool::from(p2.is_torsion_free()) {
return None;
}
let mut result = PodG2Point(p2.to_uncompressed());
if matches!(endianness, Endianness::LE) {
swap_g2_c0_c1(&mut result.0);
swap_fq_endianness(&mut result.0);
}
Some(result)
}
#[cfg(test)]
mod tests {
use {super::*, crate::test_vectors::*, bytemuck::pod_read_unaligned};
fn to_pod_g1(bytes: &[u8]) -> PodG1Point {
pod_read_unaligned(bytes)
}
fn to_pod_g2(bytes: &[u8]) -> PodG2Point {
pod_read_unaligned(bytes)
}
// New helpers for compressed pods
fn to_pod_g1_compressed(bytes: &[u8]) -> PodG1Compressed {
pod_read_unaligned(bytes)
}
fn to_pod_g2_compressed(bytes: &[u8]) -> PodG2Compressed {
pod_read_unaligned(bytes)
}
fn run_g1_test(
test_name: &str,
input_be: &[u8],
output_be: Option<&[u8]>,
input_le: &[u8],
output_le: Option<&[u8]>,
) {
// Test Big Endian
let input_be_pod = to_pod_g1_compressed(input_be);
let result_be = bls12_381_g1_decompress(Version::V0, &input_be_pod, Endianness::BE);
match output_be {
Some(expected) => assert_eq!(
result_be,
Some(to_pod_g1(expected)),
"G1 {test_name} BE Test Failed",
),
None => assert!(result_be.is_none(), "G1 {test_name} BE expected failure"),
}
// Test Little Endian
let input_le_pod = to_pod_g1_compressed(input_le);
let result_le = bls12_381_g1_decompress(Version::V0, &input_le_pod, Endianness::LE);
match output_le {
Some(expected) => assert_eq!(
result_le,
Some(to_pod_g1(expected)),
"G1 {test_name} LE Test Failed",
),
None => assert!(result_le.is_none(), "G1 {test_name} LE expected failure"),
}
}
fn run_g2_test(
test_name: &str,
input_be: &[u8],
output_be: Option<&[u8]>,
input_le: &[u8],
output_le: Option<&[u8]>,
) {
// Test Big Endian
let input_be_pod = to_pod_g2_compressed(input_be);
let result_be = bls12_381_g2_decompress(Version::V0, &input_be_pod, Endianness::BE);
match output_be {
Some(expected) => assert_eq!(
result_be,
Some(to_pod_g2(expected)),
"G2 {test_name} BE Test Failed",
),
None => assert!(result_be.is_none(), "G2 {test_name} BE expected failure"),
}
// Test Little Endian
let input_le_pod = to_pod_g2_compressed(input_le);
let result_le = bls12_381_g2_decompress(Version::V0, &input_le_pod, Endianness::LE);
match output_le {
Some(expected) => assert_eq!(
result_le,
Some(to_pod_g2(expected)),
"G2 {test_name} LE Test Failed",
),
None => assert!(result_le.is_none(), "G2 {test_name} LE expected failure"),
}
}
#[test]
fn test_g1_decompress_random() {
run_g1_test(
"Decompress: RANDOM",
INPUT_BE_G1_DECOMPRESS_RANDOM,
Some(OUTPUT_BE_G1_DECOMPRESS_RANDOM),
INPUT_LE_G1_DECOMPRESS_RANDOM,
Some(OUTPUT_LE_G1_DECOMPRESS_RANDOM),
);
}
#[test]
fn test_g1_decompress_infinity() {
run_g1_test(
"Decompress: INFINITY",
INPUT_BE_G1_DECOMPRESS_INFINITY,
Some(OUTPUT_BE_G1_DECOMPRESS_INFINITY),
INPUT_LE_G1_DECOMPRESS_INFINITY,
Some(OUTPUT_LE_G1_DECOMPRESS_INFINITY),
);
}
#[test]
fn test_g1_decompress_generator() {
run_g1_test(
"Decompress: GENERATOR",
INPUT_BE_G1_DECOMPRESS_GENERATOR,
Some(OUTPUT_BE_G1_DECOMPRESS_GENERATOR),
INPUT_LE_G1_DECOMPRESS_GENERATOR,
Some(OUTPUT_LE_G1_DECOMPRESS_GENERATOR),
);
}
#[test]
fn test_g1_decompress_invalid() {
run_g1_test(
"Decompress: INVALID_CURVE",
INPUT_BE_G1_DECOMPRESS_RANDOM_INVALID_CURVE,
None,
INPUT_LE_G1_DECOMPRESS_RANDOM_INVALID_CURVE,
None,
);
run_g1_test(
"Decompress: INVALID_FIELD",
INPUT_BE_G1_DECOMPRESS_FIELD_TOO_LARGE_INVALID,
None,
INPUT_LE_G1_DECOMPRESS_FIELD_TOO_LARGE_INVALID,
None,
);
}
#[test]
fn test_g2_decompress_random() {
run_g2_test(
"Decompress: RANDOM",
INPUT_BE_G2_DECOMPRESS_RANDOM,
Some(OUTPUT_BE_G2_DECOMPRESS_RANDOM),
INPUT_LE_G2_DECOMPRESS_RANDOM,
Some(OUTPUT_LE_G2_DECOMPRESS_RANDOM),
);
}
#[test]
fn test_g2_decompress_infinity() {
run_g2_test(
"Decompress: INFINITY",
INPUT_BE_G2_DECOMPRESS_INFINITY,
Some(OUTPUT_BE_G2_DECOMPRESS_INFINITY),
INPUT_LE_G2_DECOMPRESS_INFINITY,
Some(OUTPUT_LE_G2_DECOMPRESS_INFINITY),
);
}
#[test]
fn test_g2_decompress_generator() {
run_g2_test(
"Decompress: GENERATOR",
INPUT_BE_G2_DECOMPRESS_GENERATOR,
Some(OUTPUT_BE_G2_DECOMPRESS_GENERATOR),
INPUT_LE_G2_DECOMPRESS_GENERATOR,
Some(OUTPUT_LE_G2_DECOMPRESS_GENERATOR),
);
}
#[test]
fn test_g2_decompress_invalid() {
run_g2_test(
"Decompress: INVALID_CURVE",
INPUT_BE_G2_DECOMPRESS_RANDOM_INVALID_CURVE,
None,
INPUT_LE_G2_DECOMPRESS_RANDOM_INVALID_CURVE,
None,
);
run_g2_test(
"Decompress: INVALID_FIELD",
INPUT_BE_G2_DECOMPRESS_FIELD_TOO_LARGE_INVALID,
None,
INPUT_LE_G2_DECOMPRESS_FIELD_TOO_LARGE_INVALID,
None,
);
}
}