Cardano Improvement Proposals

CIP 49 - ECDSA and Schnorr signatures in Plutus Core


Simple Summary

Support ECDSA and Schnorr signatures over the SECP256k1 curve in Plutus Core; specifically, allow validation of such signatures as builtins.


Provides a way of verifying ECDSA and Schnorr signatures over the SECP256k1 curve in Plutus Core, specifically with new builtins. These builtins work over BuiltinByteStrings.


Signature schemes based on the SECP256k1 curve are common in the blockchain industry; a notable user of these is Bitcoin. Supporting signature schemes which are used in other parts of the industry provides an interoperability benefit: we can verify signatures produced by other systems as they are today, without requiring other people to produce signatures specifically for us. This not only provides us with improved interoperability with systems based on Bitcoin, but also compatibility with other interoperability systems, such as Wanchain and Renbridge, which use SECP256k1 signatures for verification. Lastly, if we can verify Schnorr signatures, we can also verify Schnorr-compatible multi or threshold signatures, such as MuSig2 or Frost.


Two new builtin functions would be provided:

These would be based on secp256k1, a reference implementation of both kinds of signature scheme in C. This implementation would be called from Haskell using direct bindings to C. These bindings would be defined in cardano-base, using its existing DSIGN interface, with new builtins in Plutus Core on the basis of the DSIGN interface for both schemes.

The builtins would be costed as follows: ECDSA signature verification has constant cost, as the message, verification key and signature are all fixed-width; Schnorr signature verification is instead linear in the message length, as this can be arbitrary, but as the length of the verification key and signature are constant, the costing will be constant in both.

More specifically, Plutus would gain the following primitive operations:

Both functions take parameters of a specific part of the signature scheme, even though they are all encoded as BuiltinByteStrings. In order, for both functions, these are:

  1. A verification key;
  2. An input to verify (either the message itself, or a hash);
  3. A signature.

The two different schemes handle deserialization internally: specifically, there is a distinction made between 'external' representations, which are expected as arguments, and 'internal' representations, used only by the implementations themselves. This creates different expecations for each argument for both of these schemes; we describe these below.

For the ECDSA signature scheme, the requirements are as follows. Note that these differ from the serialization used by Bitcoin, as the serialisation of signatures uses DER-encoding, which result in variable size signatures up to 72 bytes (instead of the 64 byte encoding we describe in this document).

          ┃ r <32 bytes> │ s <32 bytes>  ┃
          <--------- signature ---------->

For the Schnorr signature scheme, we have the following requirements, as described in the requirements for BIP-340:

          ┃ R <32 bytes> │ s <32 bytes>  ┃
          <--------- signature ---------->

The builtin operations will error with a descriptive message if given inputs that don't correspond to the constraints above, return False if the signature fails to verify the input given the key, and True otherwise.


We consider the implementation trustworthy: secp256k1 is the reference implementation for both signature schemes, and is already being used in production by Bitcoin. Specifically, ECDSA signatures over the SECP256k1 curve were used by Bitcoin before Taproot, while Schnorr signatures over the same curve have been used since Taproot.

An alternative approach could be to provide low-level primitives, which would allow any signature scheme (not just the ones under consideration here) to be implemented by whoever needs them. While this approach is certainly more flexible, it has two significant drawbacks:

It may be possible that some set of primitive can avoid both of these issues (for example, the suggestions in this CIP); in the meantime, providing direct support for commonly-used schemes such as these is worthwhile.

Backward Compatibility

At the Plutus Core level, implementing this proposal induces no backwards-incompatibility: the proposed new primitives do not break any existing functionality or affect any other builtins. Likewise, at levels above Plutus Core (such as PlutusTx), no existing functionality should be affected.

On-chain, this requires a hard fork.

Path to Active

An implementation by MLabs already exists, and has been merged into Plutus. Tests of the functionality have also been included, although costing is currently outstanding, as it cannot be done by MLabs due to limitations in how costing is calculated. Costing will instead be done by the Plutus Core team.

This CIP is licensed under Apache-2.0.