Skip to content

Expose fast additive inverse for P1/P2 in Go bindings #282

Open

Description

@leoruss0902-lab

Hi,

I noticed that the C API exposes fast conditional negation for projective points via:

blst_p1_cneg
blst_p2_cneg

However, the Go bindings do not seem to expose equivalent public methods for P1 and P2.

At the moment, if one wants to compute the additive inverse of a point in Go, i.e. P -> -P, the closest public approach seems to be using subtraction, for example computing 0 - P through SubAssign.

This works mathematically, but it is significantly more expensive than direct group negation, because subtraction goes through point addition logic.

I tested a small local patch exposing direct negation in the Go bindings, using a small cgo wrapper around blst_p1_cneg.

Example API:

func (p1 *P1) NegAssign() *P1 {
    C.go_p1_cneg(&p1.cgo)
    return p1
}

With the corresponding C bridge:

static void go_p1_cneg(blst_p1 *p)
{
    blst_p1_cneg(p, 1);
}

On my machine, using subtraction to compute -P gave approximately:

755 ns/op
0 B/op
0 allocs/op

while direct blst_p1_cneg through the local Go binding patch gave approximately:

65 ns/op
0 B/op
0 allocs/op

So this is roughly an 11x improvement for this operation in my benchmark.

Would you be open to adding direct group negation methods to the Go bindings?

Possible API names could be:

func (p1 *P1) NegAssign() *P1

func (p2 *P2) NegAssign() *P2

Thank you.

Activity

  1. dot-asm commented on May 14, 2026

    @dot-asm
    Collaborator

    Here is the trouble. In Go one wants to minimize the calls to ultrafast C subroutines, because the cgo call overhead overshadows the actual useful computation. One can also put it as following, if such short C subroutine ends up on critical path, then you want to do something different. Could you clarify what is it you're trying to do? If you negate a point and then add it to some other point, then you can just as well subtract it. If you negate, multiply and add, then you can multiply and subract...

  2. leoruss0902-lab commented on May 14, 2026

    @leoruss0902-lab
    Author

    Hi,

    Thanks for the reply and the clear explanation. You are absolutely right about the cgo call overhead overshadowing the math for such a tiny operation.

    To clarify my use case: I am not using the negated point for standard elliptic curve arithmetic. If I were, I would definitely use SubAssign to minimize cgo crossings as you suggested.

    Instead, I am implementing a NIZK verifier where I need to check pairing equations, e.g., $e(A, B) = e(C, D)$.
    To optimize this and avoid multiple expensive $\mathbb{F}_{p^{12}}$ exponentiations, I am batching the pairings using Miller loops and a single final exponentiation, evaluating:
    $e(A, B) \cdot e(-C, D) = 1$

    Because of this, I need to explicitly compute $-C$ and pass it directly as an isolated input to the Miller loop function.

    Right now, the only public Go API way I found to get $-C$ is by doing P1.SubAssign(C), after having initialized P1 to its zero value (computing $0 - C$), which goes through the full point addition logic rather than a simple conditional negation.

    Given this specific use case for pairing verifications, what would be your recommended approach? Would you be open to exposing a negation method, or is there perhaps a way to negate the point purely in Go (manipulating the struct limbs directly) to avoid the cgo overhead entirely?

    Thanks again for your time!

  3. dot-asm commented on May 15, 2026

    @dot-asm
    Collaborator

    Ah! The answer to the question is Fp12FinalVerify. Not only does it accept $e(A,B)$ and $e(C,D)$ as inputs, but it even takes you all the way to ==1.

  4. leoruss0902-lab commented on May 28, 2026

    @leoruss0902-lab
    Author

    Hi,

    thank you again for the clarification.

    I looked a bit more into the C implementation, and I think I now understand the role of Fp12FinalVerify better. For comparing two Miller-loop outputs, it is clearly the intended API, since it avoids the naive approach of doing two separate final exponentiations.

    My original point was about a slightly different cost comparison. In both approaches the Miller-loop outputs still have to be multiplied/combined before the final exponentiation. The difference I had in mind is where the “inverse/sign change” is applied: if the equation can be arranged by negating one input point before the Miller loop, this only costs a point negation, while Fp12FinalVerify handles the corresponding operation on the Fp12 side before the final exponentiation.

    So my intuition is that there may be a small constant-factor advantage in the point-negation approach in that specific case. That said, I understand that this is likely small compared to the total cost of the Miller loops and the final exponentiation, and in Go the extra cgo calls needed to do this manually may reduce or even eliminate that advantage in practice.

    So I agree that Fp12FinalVerify is the right interface for comparing pairing products. My main point is just that exposing point negation in the Go bindings still seems useful as a natural group operation, especially since the C API already exposes blst_p1_cneg and blst_p2_cneg.

    Thanks again for your time and for the explanation.

  5. dot-asm commented on Jun 3, 2026

    @dot-asm
    Collaborator

    So my intuition is that there may be a small constant-factor advantage in the point-negation approach in that specific case.

    No, because the number of cgo transitions would be higher.

    exposing point negation in the Go bindings still seems useful as a natural group operation

    Again, "if such short C subroutine ends up on critical path, then you want to do something different." Or in other words, you don't want the interfaces to be exposed ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions