BLS12-381 Elliptic Curve Syscalls
- Idea
- Draft
- Review
- Accept
- Implement
- Active
Decision brief
Why this proposal matters
This proposal introduces a new family of syscalls to provide native support for cryptographic operations on the BLS12-381 elliptic curve. These syscalls will expose: Group operations (addition, subtraction/negation, and scalar multiplication) in G1 and G2 • Point validation in G1 and G2 • Pairing operation • Decompression operations in G1 and G2 Poseidon hash support is outside the scope of this SIMD.
Proposal at a glance
What changes
- Proof of Possession (PoP): A cryptographic proof that an entity (e.g., a validator) holds the secret private key corresponding to a public key that they are attempting to register. In the context of BLS, this is typically a signature created with the private key over a message derived from the public key itself.
- Group operations (addition, subtraction/negation, and scalar multiplication) in G1 and G2
- Point validation in G1 and G2
Stakeholder map
Who is affected
Builders & client teams Medium impact
Reasoning: Enforcing the subgroup check allows the runtime to safely use faster endomorphism-based scalar multiplication algorithms (e.g., GLV), which are only valid for points in the correct subgroup.
Action requirement unknownValidators & operators Impact unknown
No proposal-specific evidence was found for this group.
Action requirement unknownUsers & stakers Impact unknown
No proposal-specific evidence was found for this group.
Action requirement unknownGovernance & ecosystem Medium impact
This proposal introduces a new family of syscalls to provide native support for cryptographic operations on the BLS12-381 elliptic curve. These syscalls will expose:
Action requirement unknownExact source revision
Full proposal document
0616093b2952Summary
This proposal introduces a new family of syscalls to provide native support for cryptographic operations on the BLS12-381 elliptic curve. These syscalls will expose:
- Group operations (addition, subtraction/negation, and scalar multiplication) in G1 and G2
- Point validation in G1 and G2
- Pairing operation
- Decompression operations in G1 and G2
Poseidon hash support is outside the scope of this SIMD.
Motivation
Solana's current support for pairing-based cryptography is limited to the
alt_bn128 (also known as BN254) curve. Pairing-friendly curves are the
foundation for many efficient and modern zero-knowledge proof systems, such as
those based on Groth16. While the BN254 curve enables basic cryptography and
zero-knowledge applications, it does not provide a 128-bit security level, which
is insufficient for high-security protocols.
The BLS12-381 curve is a modern, widely adopted standard for a pairing-friendly curve that achieves a 128-bit security level. It is adopted in other major ecosystems, such as Ethereum, which already supports it via a precompile.
Adding support for the BLS12-381 curve is important for enabling Alpenglow consensus (SIMD 326) as well. Alpenglow consensus votes themselves do not require a BLS12-381 syscall as they are processed differently than regular transactions.
However, when a validator registers their BLS public key, it must do so using a regular on-chain transaction that will pass through the normal transaction pipeline. This transaction must contain a BLS Proof of Possession (PoP) to validate ownership of the BLS public key. This PoP is a cryptographic proof that is crucial to prevent a "rogue-key-attack", where a malicious validator could try to register a key that is an algebraic combination of their own key and a victim's key. This would allow the attacker to forge aggregate signatures that appear to come from the entire group, even without the victim's participation.
Initially, the vote program will natively execute PoP verification (see SIMD-0387). However, when the vote program eventually transitions to BPF, it will require a way to efficiently verify PoP inside a BPF program. By introducing syscalls for BLS12-381, a standard BPF program can efficiently verify these PoPs.
New Terminology
Proof of Possession (PoP): A cryptographic proof that an entity (e.g., a validator) holds the secret private key corresponding to a public key that they are attempting to register. In the context of BLS, this is typically a signature created with the private key over a message derived from the public key itself.
Rogue-Key Attack: An attack against aggregate signature schemes (like BLS) where a malicious user registers a "rogue" public key that is mathematically constructed from their own key and one or more victim validators' public keys. This allows the attacker to forge aggregate signatures that appear to be validly signed by the entire group (including the victims), even without the victims' participation. Requiring a PoP at registration prevents this attack because the attacker cannot prove possession of the secret key for the rogue public key.
Detailed Design
We propose the addition of the following operations on BLS12-381 as syscalls:
- Group operations (addition, subtraction/negation, and scalar multiplication) in G1 and G2
- Point validation in G1 and G2
- Pairing operation
- Decompression operations in G1 and G2
These operations are sufficient to enable standard pairing-based cryptography applications, for example, Groth16 proof verifier on BLS12-381 and BLS signature or Proof of Possession (PoP) verifier. Additional operations are needed to support more advanced ZK or cryptography applications, but these are outside the scope of this SIMD.
Curve Specification and Endianness (LE vs. BE)
For the curve definition, the IETF draft should be used as reference. For the encoding, the Zcash specification should be used. We note that the Zcash specification defines a canonical big-endian (BE) encoding. To add the most flexibility, we also define parallel little-endian (LE) variants.
Our LE variants mirror the Zcash standard in structure, with the only change being the byte-ordering of the base field (Fq) elements themselves:
Fq Elements: A 381-bit field element is encoded in 48 bytes.
_BEvariants expect this 48-byte array in big-endian._LEvariants expect this 48-byte array in little-endian.
Fq^2 Element Ordering: An Fq^2 element
(c0 + c1 * u)is encoded as 96 bytes._BEvariants: The Zcash standard's structural ordering is preserved. The 48-byte encoding of thec1component (imaginary) comes first, followed by the 48-byte encoding of thec0component (real)._LEvariants: The structural ordering follows the canonical memory layout of Rust structs and Little-Endian polynomial representation. The 48-byte encoding of thec0component (real) comes first, followed by the 48-byte encoding of thec1component (imaginary).
Compressed Point Control Bits: For compressed representations (used by
sol_curve_decompress), the Zcash standard uses the 3 most-significant-bits of the entire byte string (e.g., the first bits of the 48-byte G1 array) for flags (compression, infinity, and sign)._BEvariants: The flags are located in the 3 most significant-bits of the first byte (offset 0) of the 48-byte array._LEvariants: The flags are located in the 3 most-significant-bits of the last byte (offset 47) of the 48-byte array.
New Curve ID Constants
We propose using new curve_id constants for BLS12-381. These IDs will be used
across across the new and extended syscalls (sol_curve_group_op,
sol_curve_validate_point, sol_curve_pairing_map, and sol_curve_decompress)
to specify the curve and endianness.
pub const CURVE25519_EDWARDS: u64 = 0;
pub const CURVE25519_RISTRETTO: u64 = 1;
// Reserve indices 2 and 3 in case we want to support affine representations of
// curve25519 points in the future
// pub const CURVE25519_EDWARDS_AFFINE_LE: u64 = 2;
// pub const CURVE25519_EDWARDS_AFFINE_BE: u64 = 2 | 0x80;
// pub const CURVE25519_RISTRETTO_AFFINE_LE: u64 = 3;
// pub const CURVE25519_RISTRETTO_AFFINE_BE: u64 = 3 | 0x80;
// New Curve ID
pub const BLS12_381_LE: u64 = 4;
pub const BLS12_381_BE: u64 = 4 | 0x80;
pub const BLS12_381_G1_LE: u64 = 5;
pub const BLS12_381_G1_BE: u64 = 5 | 0x80;
pub const BLS12_381_G2_LE: u64 = 6;
pub const BLS12_381_G2_BE: u64 = 6 | 0x80;
The BLS12-381_{LE,BE} constants will be used for the pairing operation.
The BLS12_381_G1_{LE,BE} and BLS12_381_G2_{LE,BE} constants will be used for
group operations and decompression.
Addition and Scalar Multiplication on G1 and G2
There is already a dedicated sol_curve_group_op syscall function for general
elliptic curve group operations. This function takes in a curve_id, group_op,
and two scalar/points that are encoded in little-endian. It interprets the
curve points according to the curve id, and applies the group operations
specified by the group_op. Currently, the syscall supports the curve25519
edwards and ristretto representations.
This function can be extended to support the addition, subtraction, and scalar
multiplication in BLS12-381 G1 and G2. The syscall will use the newly defined
BLS12_381_G1_{BE,LE} and BLS12_381_G2_{BE,LE} curve IDs. The syscall will
interpret inputs differently based on the curve_id:
- BLS12-381 inputs (using
BLS12_381_G1_{BE,LE},BLS12_381_G2_{BE,LE}) will be interpreted as points in affine representation in either little-endian or big-endian. - Curve25519 inputs (using
CURVE25519_EDWARDSorCURVE25519_RISTRETTO) are interpreted as points in their respective Edwards or Ristretto representations in compressed little-endian representations.
For clarity, the group_op parameter uses the following existing constants,
which will now also apply to the BLS12-381 curve IDs:
pub const ADD: u64 = 0;
pub const SUB: u64 = 1;
pub const MUL: u64 = 2;
The validation requirements differ by operation type to balance safety and compute cost:
Addition (
ADD) and Subtraction (SUB): Input points MUST be validated to ensure that they are on the curve (satisfy the curve equation). However, the subgroup check is skipped.- Reasoning: The addition formulas are valid for any point on the curve, even those not in the prime-order subgroup. Skipping the costly subgroup check allows for cheaper accumulation of points, assuming the final result will be validated or processed by an operation that enforces it.
Scalar Multiplication (
MUL): Input points MUST undergo full validation, including the field check, curve equation check, and the subgroup check.- Reasoning: Enforcing the subgroup check allows the runtime to safely use faster endomorphism-based scalar multiplication algorithms (e.g., GLV), which are only valid for points in the correct subgroup.
Point Validation in G1 and G2
There is an existing definition for a sol_curve_validate_point syscall
function for point validation, which currently supports Curve25519 Edwards and
Ristretto points. We propose extending this syscall to also support validation
for BLS12-381 G1 and G2 points.
define_syscall!(fn sol_curve_validate_point(
curve_id: u64,
point_addr: *const u8,
result: *mut u8
) -> u64)
When called with BLS12_381_G1_{LE,BE} or BLS12_381_G2_{LE,BE} curve IDs,
this function will perform a full validation on the input point, which is
interpreted as an uncompressed affine point (96 bytes for G1, 192 bytes
for G2). The endianness flag (_LE or _BE) dictates how the underlying
field elements are read.
For the operation to succeed (return 0), the input bytes must pass all of the following checks:
- Field Check: The coordinate(s) are valid field elements (i.e., less than the
modulus
p). - On-Curve Check: The point satisfies the curve equation.
- Subgroup Check: The point is in the correct prime-order subgroup
r.
This syscall is specifically for uncompressed affine points. To validate a
compressed point, programs can use the sol_curve_decompress syscall, which
performs identical validation checks as part of its decompression process.
While it is possible to validate an uncompressed affine point (x, y) by using
the sol_curve_decompress function (e.g., by manually setting the control bits
based on y and feeding the x coordinate to the decompressor, then comparing
the result y' with y), this approach is inefficient. It would require the
syscall to perform a costly square root operation to find y'.
The sol_curve_validate_point syscall is more efficient as it can directly
check the curve equation and perform the subgroup check without computing a
square root, making it a cheaper, dedicated operation for on-chain programs that
only need to validate existing affine points.
Pairing Operation
There is an existing definition for a sol_curve_pairing_map syscall function for
elliptic curve pairing operations.
define_syscall!(fn sol_curve_pairing_map(
curve_id: u64,
point: *const u8,
result: *mut u8
) -> u64);
This function is not actually instantiated at the moment. We propose updating this function's signature to take in an array of points in G1 and an array of points in G2 to support batch pairings.
define_syscall!(fn sol_curve_pairing_map(
curve_id: u64,
num_pairs: u64,
g1_points: *const u8,
g2_points: *const u8,
result: *mut u8
) -> u64);
The num_pairs parameter specifies the number of G1/G2 pairs to be processed.
To ensure bounded execution time and prevent excessive compute consumption,
num_pairs is strictly bounded to a maximum of 8. The g1_points and g2_points
parameters are pointers to memory buffers.
The syscall will read num_pairs from each buffer. It must read:
num_pairs * 96bytes from theg1_pointsbuffer (96 bytes per uncompressed affine G1 point).num_pairs * 192bytes from theg2_pointsbuffer (192 bytes per uncompressed affine G2 point). If thenum_pairsis 0, the syscall should return success with the identity element of the target group.
The runtime must safely handle memory accesses. If reading the required number
of bytes from either buffer results in an out-of-bounds memory access (e.g., the
buffers are smaller than implied by num_pairs), the syscall must return an
error.
The function would interpret g1_points as an array of BLS12-381 points in G1
[P1, ..., Pn] and g2_points as an array of BLS12-381 points in G2
[Q1, ..., Qn]. Both inputs are interpreted as affine representations
encoded in either little-endian or big-endian depending on the curve id. It
should then compute the pairing product e(P1, Q1) * ... * e(Pn, Qn).
The result of the syscall will be the actual target group element from the
product of pairings.
Target Group (Gt) Encoding
The pairing operation returns an element in the target group Gt, which is a
subgroup of the field extension (of degree 12) field Fq12. An element in
Fq12 is represented as a tower of extensions:
Fq12is a quadratic extension ofFq6:Fq12 = c0 + c1 * wFq6is a cubic extension ofFq2:Fq6 = c0 + c1 * v + c2 * v^2Fq2is a quadratic extension ofFq:Fq2 = c0 + c1 * u
This results in 12 base field coefficients (Fq), usually denoted as
c0 through c11 in increasing order of their "tower" degree.
The serialization of the 576-byte Gt element follows these rules:
Little-Endian (LE):
- Coefficient Ordering: Canonical memory order (lowest degree first).
The layout is
c0, c1, ..., c11. - Byte Ordering: Each 48-byte
Fqcoefficient is encoded in little-endian.
- Coefficient Ordering: Canonical memory order (lowest degree first).
The layout is
Big-Endian (BE):
- Coefficient Ordering: Highest degree first (reverses the tower).
The layout is
c11, c10, ..., c0. - Byte Ordering: Each 48-byte
Fqcoefficient is encoded in big-endian.
- Coefficient Ordering: Highest degree first (reverses the tower).
The layout is
Implementation Note: The Big-Endian format effectively corresponds to reversing
the entire 576-byte Little-Endian array. This operation simultaneously swaps the
coefficient ordering (from c0...c11 to c11...c0) and converts every
underlying Fq element from little-endian to big-endian.
For example, the multiplicative identity 1 in Gt is represented as:
- LE:
01 00 ... 00(The first byte is0x01, followed by 575 zeros). This representsc0 = 1, and all other coefficients0. - BE:
00 ... 00 01(575 zeros, followed by0x01as the last byte). This representsc0 = 1as the last coefficient in the stream.
Decompression Operations in G1 and G2
For the decompression operations in G1 and G2, we propose adding a dedicated
syscall for general decompression sol_curve_decompress.
define_syscall!(fn sol_curve_decompress(
curve_id: u64,
point: *const u8,
result: *mut u8
) -> u64);
This function will take in a curve id and a point that is represented in its compressed representation (encoded in little-endian or big-endian). It will decompress the compressed point into an affine representation and write the result.
This function must perform a full point validation as specified in the Zcash specification. For the operation to succeed, the input bytes must pass all of the following checks:
- Format Check: The control bits (compression, infinity, and sign) in the most-significant bits are valid as per the Zcash standard.
- Field Check: The
x-coordinate is a valid field element (i.e., less than the modulusp). - On-Curve Check: The point satisfies the curve equation.
- Subgroup Check: The resulting point is in the correct prime-order subgroup
r.
Alternatives Considered
This proposal extends the existing sol_curve_group_op and
sol_curve_pairing_map syscalls to add support for BLS12-381. The main design
alternative would be to add separate dedicated syscall functions for BLS12-381
as was done for the BN254 curve.
We chose to extend the existing syscalls for few reasons:
It follows the established pattern. The
sol_curve_group_opsyscall was designed to be extensible, using acurve_idto handle different curves likeCURVE25519_EDWARDSandCURVE25519_RISTRETTO. Adding a newcurve_idfor BLS12-381 is a consistent and logical way to extend it.It avoids syscall bloat. New curves will keep coming and if we add a new, dedicated syscall for every single one, that would make the runtime hard to maintain and audit. A generic interface is cleaner.
It's a simpler and safer code change. Adding a new
curve_idto an existingmatchstatement is a small, localized, and low-risk change. Plumbing an entirely new syscall is a much more complex and error-prone task.
Impact
This will enable ZK and other more advanced pairing-based cryptography applications to be built on Solana based on a more secure and modern curve.
Security Considerations
The necessary security considerations such as point validation, memory safety, etc. are detailed in the Detailed Design section.
Evidence graph
Related proposals and rollout
One or more sources are unavailable.
Upstream review record
Upstream discussion & review
One or more sources are unavailable.
Provenance
Evidence & technical details
Rollout or chain data, source revisions, freshness and integrity. 3
Provenance
Evidence & technical details
Rollout or chain data, source revisions, freshness and integrity.SIMD
Deployment Status
Feature Gate not yet created
Exact source revision
Sources & integrity
- Proposal document pinned_commit_blob
0616093b2952ed6de52c4a27d66aadd11d48d4f9 - simd-document document · stale · Sep 11, 2026
0616093b2952ed6de52c4a27d66aadd11d48d4f9
One or more sources are unavailable.
One or more sources are unavailable.
simd.watch community discussion · SIMD-0388
Powered by Giscus · Sign in with GitHub to comment
Community comments load when this section approaches the viewport.