Skip to main content

fluentbase_runtime/syscall_handler/weierstrass/
weierstrass_double.rs

1use crate::{syscall_handler::syscall_process_exit_code, RuntimeContext};
2use fluentbase_types::{
3    ExitCode, BLS12381_G1_RAW_AFFINE_SIZE, BN254_G1_RAW_AFFINE_SIZE, SECP256K1_G1_RAW_AFFINE_SIZE,
4    SECP256R1_G1_RAW_AFFINE_SIZE,
5};
6use num::BigUint;
7use rwasm::{StoreTr, TrapCode, Value};
8use sp1_curves::{
9    params::FieldParameters,
10    weierstrass::{
11        bls12_381::{Bls12381, Bls12381BaseField},
12        bn254::{Bn254, Bn254BaseField},
13        secp256k1::{Secp256k1, Secp256k1BaseField},
14        secp256r1::{Secp256r1, Secp256r1BaseField},
15    },
16    AffinePoint, EllipticCurve,
17};
18
19pub fn syscall_secp256k1_double_handler(
20    ctx: &mut impl StoreTr<RuntimeContext>,
21    params: &[Value],
22    result: &mut [Value],
23) -> Result<(), TrapCode> {
24    syscall_weierstrass_double_handler::<
25        Secp256k1,
26        Secp256k1BaseField,
27        { SECP256K1_G1_RAW_AFFINE_SIZE },
28    >(ctx, params, result)
29}
30pub fn syscall_secp256r1_double_handler(
31    ctx: &mut impl StoreTr<RuntimeContext>,
32    params: &[Value],
33    result: &mut [Value],
34) -> Result<(), TrapCode> {
35    syscall_weierstrass_double_handler::<
36        Secp256r1,
37        Secp256r1BaseField,
38        { SECP256R1_G1_RAW_AFFINE_SIZE },
39    >(ctx, params, result)
40}
41pub fn syscall_bn254_double_handler(
42    ctx: &mut impl StoreTr<RuntimeContext>,
43    params: &[Value],
44    result: &mut [Value],
45) -> Result<(), TrapCode> {
46    syscall_weierstrass_double_handler::<Bn254, Bn254BaseField, { BN254_G1_RAW_AFFINE_SIZE }>(
47        ctx, params, result,
48    )
49}
50pub fn syscall_bls12381_double_handler(
51    ctx: &mut impl StoreTr<RuntimeContext>,
52    params: &[Value],
53    result: &mut [Value],
54) -> Result<(), TrapCode> {
55    syscall_weierstrass_double_handler::<Bls12381, Bls12381BaseField, { BLS12381_G1_RAW_AFFINE_SIZE }>(
56        ctx, params, result,
57    )
58}
59
60fn syscall_weierstrass_double_handler<
61    E: EllipticCurve,
62    P: FieldParameters,
63    const POINT_SIZE: usize,
64>(
65    ctx: &mut impl StoreTr<RuntimeContext>,
66    params: &[Value],
67    _result: &mut [Value],
68) -> Result<(), TrapCode> {
69    let p_ptr: u32 = params[0].i32().unwrap() as u32;
70
71    let mut p = [0u8; POINT_SIZE];
72    ctx.memory_read(p_ptr as usize, &mut p)?;
73
74    let result = syscall_weierstrass_double_impl::<E, P, POINT_SIZE>(p)
75        .map_err(|exit_code| syscall_process_exit_code(ctx, exit_code))?;
76    ctx.memory_write(p_ptr as usize, &result)?;
77
78    Ok(())
79}
80
81/// Secp256k1 curve point doubling.
82///
83/// # Input format
84/// `p` must be an affine point encoded as `[x || y]` in little-endian,
85/// where each coordinate is 32 bytes.
86///
87/// # Safety
88/// Caller must ensure coordinates are valid field elements (< modulus).
89/// No validation is performed — invalid input produces undefined output.
90pub fn syscall_secp256k1_double_impl(
91    p: [u8; SECP256K1_G1_RAW_AFFINE_SIZE],
92) -> Result<[u8; SECP256K1_G1_RAW_AFFINE_SIZE], ExitCode> {
93    syscall_weierstrass_double_impl::<Secp256k1, Secp256k1BaseField, { SECP256K1_G1_RAW_AFFINE_SIZE }>(
94        p,
95    )
96}
97
98/// Secp256r1 curve point doubling.
99///
100/// # Input format
101/// `p` must be an affine point encoded as `[x || y]` in little-endian,
102/// where each coordinate is 32 bytes.
103///
104/// # Safety
105/// Caller must ensure coordinates are valid field elements (< modulus).
106/// No validation is performed — invalid input produces undefined output.
107pub fn syscall_secp256r1_double_impl(
108    p: [u8; SECP256R1_G1_RAW_AFFINE_SIZE],
109) -> Result<[u8; SECP256R1_G1_RAW_AFFINE_SIZE], ExitCode> {
110    syscall_weierstrass_double_impl::<Secp256r1, Secp256r1BaseField, { SECP256R1_G1_RAW_AFFINE_SIZE }>(
111        p,
112    )
113}
114
115/// BN254 curve point doubling.
116///
117/// # Input format
118/// `p` must be an affine point encoded as `[x || y]` in little-endian,
119/// where each coordinate is 32 bytes.
120///
121/// # Safety
122/// Caller must ensure coordinates are valid field elements (< modulus).
123/// No validation is performed — invalid input produces undefined output.
124pub fn syscall_bn254_double_impl(
125    p: [u8; BN254_G1_RAW_AFFINE_SIZE],
126) -> Result<[u8; BN254_G1_RAW_AFFINE_SIZE], ExitCode> {
127    syscall_weierstrass_double_impl::<Bn254, Bn254BaseField, { BN254_G1_RAW_AFFINE_SIZE }>(p)
128}
129
130/// BLS12-381 curve point doubling.
131///
132/// # Input format
133/// `p` must be an affine point encoded as `[x || y]` in little-endian,
134/// where each coordinate is 48 bytes.
135///
136/// # Safety
137/// Caller must ensure coordinates are valid field elements (< modulus).
138/// No validation is performed — invalid input produces undefined output.
139pub fn syscall_bls12381_double_impl(
140    p: [u8; BLS12381_G1_RAW_AFFINE_SIZE],
141) -> Result<[u8; BLS12381_G1_RAW_AFFINE_SIZE], ExitCode> {
142    syscall_weierstrass_double_impl::<Bls12381, Bls12381BaseField, { BLS12381_G1_RAW_AFFINE_SIZE }>(
143        p,
144    )
145}
146
147fn syscall_weierstrass_double_impl<
148    E: EllipticCurve,
149    P: FieldParameters,
150    const POINT_SIZE: usize,
151>(
152    p: [u8; POINT_SIZE],
153) -> Result<[u8; POINT_SIZE], ExitCode> {
154    let (px, py) = p.split_at(p.len() / 2);
155    let (px, py) = (BigUint::from_bytes_le(px), BigUint::from_bytes_le(py));
156    // Make sure px/py are always less than modulus (to avoid neg underflow)
157    let modulus = P::modulus();
158    if px >= modulus || py >= modulus {
159        return Err(ExitCode::MalformedBuiltinParams);
160    }
161    let p_affine = AffinePoint::<E>::new(px, py);
162    let result_affine = E::ec_double(&p_affine);
163    let (rx, ry) = (result_affine.x, result_affine.y);
164    let mut result = [0u8; POINT_SIZE];
165    let mut rx = rx.to_bytes_le();
166    rx.resize(POINT_SIZE / 2, 0);
167    let mut ry = ry.to_bytes_le();
168    ry.resize(POINT_SIZE / 2, 0);
169    result[..POINT_SIZE / 2].copy_from_slice(&rx);
170    result[POINT_SIZE / 2..].copy_from_slice(&ry);
171    Ok(result)
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_bls12381_double() {
180        // generator.
181        // 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507
182        // 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569
183        let generator: [u8; 96] = [
184            187, 198, 34, 219, 10, 240, 58, 251, 239, 26, 122, 249, 63, 232, 85, 108, 88, 172, 27,
185            23, 63, 58, 78, 161, 5, 185, 116, 151, 79, 140, 104, 195, 15, 172, 169, 79, 140, 99,
186            149, 38, 148, 215, 151, 49, 167, 211, 241, 23, 225, 231, 197, 70, 41, 35, 170, 12, 228,
187            138, 136, 162, 68, 199, 60, 208, 237, 179, 4, 44, 203, 24, 219, 0, 246, 10, 208, 213,
188            149, 224, 245, 252, 228, 138, 29, 116, 237, 48, 158, 160, 241, 160, 170, 227, 129, 244,
189            179, 8,
190        ];
191
192        // 2 * generator (doubled generator).
193        // 838589206289216005799424730305866328161735431124665289961769162861615689790485775997575391185127590486775437397838
194        // 3450209970729243429733164009999191867485184320918914219895632678707687208996709678363578245114137957452475385814312
195        let expected_doubled: [u8; 96] = [
196            78, 15, 191, 41, 85, 140, 154, 195, 66, 124, 28, 143, 187, 117, 143, 226, 42, 166, 88,
197            195, 10, 45, 144, 67, 37, 1, 40, 145, 48, 219, 33, 151, 12, 69, 169, 80, 235, 200, 8,
198            136, 70, 103, 77, 144, 234, 203, 114, 5, 40, 157, 116, 121, 25, 136, 134, 186, 27, 189,
199            22, 205, 212, 217, 86, 76, 106, 215, 95, 29, 2, 185, 59, 247, 97, 228, 112, 134, 203,
200            62, 186, 34, 56, 142, 157, 119, 115, 166, 253, 34, 163, 115, 198, 171, 140, 157, 106,
201            22,
202        ];
203
204        let result = syscall_bls12381_double_impl(generator).unwrap();
205        assert_eq!(result, expected_doubled);
206    }
207
208    #[test]
209    fn test_bn254_double() {
210        for _ in 0..10i64.pow(3) {
211            // generator.
212            // 1
213            // 2
214            let a: [u8; 64] = [
215                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,
216                0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
217                0, 0, 0, 0, 0, 0, 0, 0,
218            ];
219            // 2 * generator.
220            // 1368015179489954701390400359078579693043519447331113978918064868415326638035
221            // 9918110051302171585080402603319702774565515993150576347155970296011118125764
222            let b: [u8; 64] = [
223                211, 207, 135, 109, 193, 8, 194, 211, 168, 28, 135, 22, 169, 22, 120, 217, 133, 21,
224                24, 104, 91, 4, 133, 155, 2, 26, 19, 46, 231, 68, 6, 3, 196, 162, 24, 90, 122, 191,
225                62, 255, 199, 143, 83, 227, 73, 164, 166, 104, 10, 156, 174, 178, 150, 95, 132,
226                231, 146, 124, 10, 14, 140, 115, 237, 21,
227            ];
228
229            let result = syscall_bn254_double_impl(a).unwrap();
230            assert_eq!(result, b);
231        }
232    }
233
234    #[test]
235    fn test_secp256k1_double() {
236        for _ in 0..10 {
237            // generator.
238            // 55066263022277343669578718895168534326250603453777594175500187360389116729240
239            // 32670510020758816978083085130507043184471273380659243275938904335757337482424
240            let a: [u8; 64] = [
241                152, 23, 248, 22, 91, 129, 242, 89, 217, 40, 206, 45, 219, 252, 155, 2, 7, 11, 135,
242                206, 149, 98, 160, 85, 172, 187, 220, 249, 126, 102, 190, 121, 184, 212, 16, 251,
243                143, 208, 71, 156, 25, 84, 133, 166, 72, 180, 23, 253, 168, 8, 17, 14, 252, 251,
244                164, 93, 101, 196, 163, 38, 119, 218, 58, 72,
245            ];
246
247            // 2 * generator.
248            // 89565891926547004231252920425935692360644145829622209833684329913297188986597
249            // 12158399299693830322967808612713398636155367887041628176798871954788371653930
250            let b: [u8; 64] = [
251                229, 158, 112, 92, 185, 9, 172, 171, 167, 60, 239, 140, 75, 142, 119, 92, 216, 124,
252                192, 149, 110, 64, 69, 48, 109, 125, 237, 65, 148, 127, 4, 198, 42, 229, 207, 80,
253                169, 49, 100, 35, 225, 208, 102, 50, 101, 50, 246, 247, 238, 234, 108, 70, 25, 132,
254                197, 163, 57, 195, 61, 166, 254, 104, 225, 26,
255            ];
256
257            let result = syscall_secp256k1_double_impl(a).unwrap();
258            assert_eq!(result, b);
259        }
260    }
261
262    #[test]
263    fn test_secp256r1_double() {
264        // generator.
265        // 48439561293906451759052585252797914202762949526041747995844080717082404635286
266        // 36134250956749795798585127919587881956611106672985015071877198253568414405109
267        let a: [u8; 64] = [
268            150, 194, 152, 216, 69, 57, 161, 244, 160, 51, 235, 45, 129, 125, 3, 119, 242, 64, 164,
269            99, 229, 230, 188, 248, 71, 66, 44, 225, 242, 209, 23, 107, 245, 81, 191, 55, 104, 64,
270            182, 203, 206, 94, 49, 107, 87, 51, 206, 43, 22, 158, 15, 124, 74, 235, 231, 142, 155,
271            127, 26, 254, 226, 66, 227, 79,
272        ];
273
274        // 2 * generator.
275        // 56515219790691171413109057904011688695424810155802929973526481321309856242040
276        // 3377031843712258259223711451491452598088675519751548567112458094635497583569
277        let b: [u8; 64] = [
278            120, 153, 102, 71, 252, 72, 11, 166, 53, 27, 242, 119, 226, 105, 137, 192, 195, 26,
279            181, 4, 3, 56, 82, 138, 126, 79, 3, 141, 24, 123, 242, 124, 209, 115, 120, 34, 157,
280            183, 4, 158, 41, 130, 233, 60, 230, 173, 125, 186, 219, 48, 116, 159, 198, 154, 61, 41,
281            64, 208, 142, 219, 16, 85, 119, 7,
282        ];
283
284        let result = syscall_secp256r1_double_impl(a).unwrap();
285        assert_eq!(result, b);
286    }
287
288    /// Reproduces chain halt when doubling an invalid BLS12-381 point with
289    /// x = 0xff..ff (above modulus) and y = 0x0.
290    #[test]
291    fn test_coords_above_modulus_halt() {
292        // 96-byte affine point: [x || y], each 48 bytes.
293        let mut coords_above_modulus_point = [0u8; 96];
294
295        // Set x = 0xff..ff (48 bytes of 0xff), y stays all zeros.
296        coords_above_modulus_point[..48].fill(0xff);
297
298        let exit_code = syscall_bls12381_double_impl(coords_above_modulus_point).unwrap_err();
299        assert_eq!(exit_code, ExitCode::MalformedBuiltinParams);
300    }
301}