Skip to main content

fluentbase_runtime/syscall_handler/edwards/
edwards_decompress.rs

1/// This code should be 100% compatible with SP1's version:
2/// - sp1/crates/core/executor/src/syscalls/precompiles/edwards/decompress.rs
3///
4/// P.S: Instead of constraint check for `sign<=1` we emit exit code (`MalformedBuiltinParams`),
5///  that must be represented inside rWasm zkVM.
6use crate::{syscall_handler::syscall_process_exit_code, RuntimeContext};
7use fluentbase_types::{ExitCode, ED25519_POINT_COMPRESSED_SIZE, ED25519_POINT_DECOMPRESSED_SIZE};
8use rwasm::{StoreTr, TrapCode, Value};
9use sp1_curves::{curve25519_dalek::CompressedEdwardsY, edwards::ed25519::decompress};
10
11pub fn syscall_ed25519_decompress_handler(
12    ctx: &mut impl StoreTr<RuntimeContext>,
13    params: &[Value],
14    _result: &mut [Value],
15) -> Result<(), TrapCode> {
16    let slice_ptr = params[0].i32().unwrap() as usize;
17    let sign = params[1].i32().unwrap() as u32;
18    let mut compressed_edwards_y = [0u8; ED25519_POINT_COMPRESSED_SIZE];
19    ctx.memory_read(
20        slice_ptr + ED25519_POINT_COMPRESSED_SIZE,
21        &mut compressed_edwards_y,
22    )?;
23    let decompressed_x_bytes = syscall_ed25519_decompress_impl(compressed_edwards_y, sign)
24        .map_err(|exit_code| syscall_process_exit_code(ctx, exit_code))?;
25    ctx.memory_write(slice_ptr, &decompressed_x_bytes)?;
26    Ok(())
27}
28
29pub fn syscall_ed25519_decompress_impl(
30    mut compressed_edwards_y: [u8; ED25519_POINT_COMPRESSED_SIZE],
31    sign: u32,
32) -> Result<[u8; ED25519_POINT_DECOMPRESSED_SIZE], ExitCode> {
33    // TODO(dmitry123): If we don't have this check, then constraint violation might happen inside SP1
34    if sign > 1 {
35        return Err(ExitCode::MalformedBuiltinParams);
36    }
37    let mut result = [0u8; ED25519_POINT_DECOMPRESSED_SIZE];
38    result[32..64].copy_from_slice(&compressed_edwards_y);
39    // Re-insert sign bit into last bit of Y for CompressedEdwardsY format
40    compressed_edwards_y[ED25519_POINT_COMPRESSED_SIZE - 1] &= 0b0111_1111;
41    compressed_edwards_y[ED25519_POINT_COMPRESSED_SIZE - 1] |= (sign as u8) << 7;
42    // Compute actual decompressed X
43    let compressed_y = CompressedEdwardsY(compressed_edwards_y);
44    let decompressed = match decompress(&compressed_y) {
45        Some(decompressed) => decompressed,
46        None => return Err(ExitCode::MalformedBuiltinParams),
47    };
48    let mut decompressed_x_bytes = decompressed.x.to_bytes_le();
49    decompressed_x_bytes.resize(32, 0u8);
50    result[0..32].copy_from_slice(decompressed_x_bytes.as_slice());
51    Ok(result)
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use fluentbase_types::hex;
58
59    /// SP1 tests are taken from: sp1/crates/test-artifacts/programs/ed-decompress/src/main.rs
60    #[test]
61    fn test_ed25519_decompress_sp1() {
62        let mut pub_bytes: [u8; 32] =
63            hex!("ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf");
64        let sign = pub_bytes[31] >> 7;
65        pub_bytes[31] &= 0b0111_1111;
66        let decompressed = syscall_ed25519_decompress_impl(pub_bytes, sign as u32).unwrap();
67        let expected: [u8; 64] = [
68            47, 252, 114, 91, 153, 234, 110, 201, 201, 153, 152, 14, 68, 231, 90, 221, 137, 110,
69            250, 67, 10, 64, 37, 70, 163, 101, 111, 223, 185, 1, 180, 88, 236, 23, 43, 147, 173,
70            94, 86, 59, 244, 147, 44, 112, 225, 36, 80, 52, 195, 84, 103, 239, 46, 253, 77, 100,
71            235, 248, 25, 104, 52, 103, 226, 63,
72        ];
73        assert_eq!(hex::encode(decompressed), hex::encode(expected));
74    }
75
76    /// Verifies that invalid Y coordinates return an error instead of panicking (DoS prevention).
77    ///
78    /// Uses Y=2 which is not on the Ed25519 curve (u/v is not a quadratic residue).
79    /// This ensures `decompress()` returns `None` and the syscall handles it gracefully
80    /// with `ExitCode::MalformedBuiltinParams` rather than crashing the VM.
81    #[test]
82    fn test_ed25519_decompress_invalid_input_returns_error() {
83        let sign = 0;
84        // Y=2: mathematically proven to not satisfy Ed25519 curve equation
85        let invalid_y: [u8; 32] =
86            hex!("0200000000000000000000000000000000000000000000000000000000000000");
87
88        let result = syscall_ed25519_decompress_impl(invalid_y, sign);
89
90        assert!(
91            result.is_err(),
92            "Expected error for invalid point, got: {:?}",
93            result
94        );
95        assert_eq!(result.unwrap_err(), ExitCode::MalformedBuiltinParams);
96    }
97}