Blobtaliptus

DevLens: secp256r1 and Schnorr Precompiles

May 27, 2025

Cover image

What a week, huh! Bitcoin price is $110k and we just celebrated the Pizza Day.

As we’re getting closer to mainnet, I wanted to highlight some interesting pieces available for developers on Citrea.

Today, let’s talk our newest additions with the Tangerine fork: secp256r1 (a.k.a. RIP-7212) and Schnorr precompiles (yes, you read that right, it’s the Schnorr of BIP 340).

But before that…

What is a precompile?

Precompiles are native functions that are directly embedded into the virtual machines. You can think of them as tiny specific programs that live just outside of the VM sandboxes. These programs are mostly designed to handle very specific operations such as hashing, elliptic curve operations, polynomical commitments. and more.

Or… in a simpler way: they are custom/complementary programs to blockchains that is always available to use. Here’s the thing: These functions could also be implemented as regular smart contracts by applications developers. Then why do we need them?

/// @notice Computes uG + vQ using Strauss-Shamir's trick on the secp256r1 elliptic curve, where G is the basepoint
/// and Q is the public key.
/// @param Q0 x-coordinate of the input point Q
/// @param Q1 y-coordinate of the input point Q
/// @param scalar_u Multiplier for basepoint G
/// @param scalar_v Multiplier for input point Q
/// @return X Resulting x-coordinate of the computed point
function mulmuladd(uint256 Q0, uint256 Q1, uint256 scalar_u, uint256 scalar_v) internal returns (uint256 X) {
    uint256 zz;
    uint256 zzz;
    uint256 Y;
    uint256 index = 255;
    uint256 [6] memory T;
    uint256 H0;
    uint256 H1;

    unchecked {
        if (scalar_u == 0 && scalar_v == 0) return 0;
        // will not work if Q=P, obvious forbidden private key
        (H0, H1) = ECDSA.affAdd(gx, gy, Q0, Q1);

        assembly {
            for { let T4 := add(shl(1, and(shr(index, scalar_v), 1)), and(shr(index, scalar_u), 1)) } eq(T4, 0) {
                index := sub(index, 1)
                T4 := add(shl(1, and(shr(index, scalar_v), 1)), and(shr(index, scalar_u), 1))
            }
            zz := add(shl(1, and(shr(index, scalar_v), 1)), and(shr(index, scalar_u), 1))

            if eq(zz, 1) {
                X := gx
                Y := gy
            }
            if eq(zz, 2) {
                X := Q0
                Y := Q1
            }
            if eq(zz, 3) {
                X := H0
                Y := H1
            }
        }
    }
    index := sub(index, 1)
    zz := 1
    zzz := 1

    // inlined EcZZ_Dbl
    for {} gt(MINUS_1, index) { index := sub(index, 1) } {
        // U = 2*Y1, y free
        let T1 := mulmod(2, Y, p)
        // V=U^2
        let T2 := mulmod(T1, T1, p)
        // S = X1*V
        // ...
    }
}

An example snippet from a smart contract library to verify ECDSA signatures made on the secp256r1 curve

Well, it is primarily because of “gas”.

Every single line in a smart contracts burns some “gas” based on how computationally heavy it is - which costs some money. Implementing these special functions as smart contracts could work, but at the end of the day you’d pay a lot of fees.

For this reason, there are these precompiles, special programs, much handier to use. They’re part of blockchain nodes, implemented in much more optimized ways, and they cost much less. Like a cheap & deterministic fast-lane that does not reside on the blockchain memory. Since it is part of nodes and not re-implemented by every developer, it is much securer too, as they are very much battle tested and available.

In short, instead of executing Solidity byte-code, the node jumps into fast, low-level code, performs a specialised task (hashing, curve maths, pairings…) and returns the result for a fraction of the gas a hand-rolled Solidity version would cost.

No deploy, no upgrade headaches. Only protocol forks can add or change them. Precompiles are good.

If you’re curious to learn more I’d recommend checking out this docs page: https://docs.citrea.xyz/developer-documentation/schnorr-secp256r1

What are the new toys?

Here's some sense on how this is useful with today's tech

Here's some sense on how this is useful with today's tech

Now lets go over the two new precompiles that are introduced to Citrea.

secp256r1 precompile

To start, most phones, laptops, and other hardwares today do signing operations with with P-256 - which is secp256r1, not Ethereum’s secp256k1. With the new precompile developers can also utilize the r1 curve to do signing operations. In Citrea, this precompile is available at the address 0x0000...0100.

Some great features that are unlocked by this precompiles are as follows:

  • Hardware & Biometric Authentication: Native support of secp256r1 allows smart contracts to directly validate the signatures from secure enclaves and passkey devices, such as Apple’s Secure Enclave, Android’s Keystore, Yubikeys, and WebAuthn authenticators.
  • Account Abstraction & Smart Wallets: Combining features above with account abstraction & smart wallets, self-custodial platforms can be built much more easily and efficiently, such as @tanaribtc. It also improves the security and the UX perspective of applications massively.
  • Gas-efficient signature verification: With this precompile, secp256r1 signature verification comes down to 3450 gas, which is significantly cheaper than any other existing smart contract verification method.
Tanaribtc

@tanaribtc is going to be huge. You have to check it out.

Now let’s check the other one

Schnorr Precompile

Schnorr signature is a Bitcoin-native, linear, and aggregation-friendly signature scheme. It is a key component of Bitcoin’s Taproot upgrade (BIP 340) and also offers advantages for multi-signature schemes like MuSig2. In Citrea, this precompile is available at the address 0x0000...0200.

This is a simplified diagram but Schnorr precompile is also very handy for BitVM bridges!

This is a simplified diagram but Schnorr precompile is also very handy for BitVM bridges!

Schnorr precompile enables an interesting set of developments, such as:

  • Scriptless cross-chain atomic swaps: With Schnorr adaptor signatures it is possible to build BTC <> cBTC atomic swaps without HTLCs or any other third-party custody.
  • Bitcoin-aware oracles & bridges: A smart contract on Citrea can now prove that a Taproot key signed some data without any external verifiers.

I guess this is it for today’s DevLens.

See you in the next DevLens writeup!


Originally published on X/Twitter