Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEP-446: Efficient BLS-signature verification #446

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions neps/nep-0446.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
NEP: 446
Title: Efficient BLS-signature verification
Author: Olga Kuniavskaia <olga.kunyavskaya@aurora.dev>
DiscussionsTo: https://github.com/nearprotocol/neps/pull/446
Status: Draft
Type: Runtime Spec
Category: Contract
Created: 24-Dec-2022
---

## Summary

A pre-compiled NEAR runtime function for verifying BLS12-381 signatures.
That enables running Ethereum 2 light clients on-chain.

## Motivation

For proper work of the Rainbow Bridge, the ETH2 Light Client should be implemented
as a NEAR contract. It has to verify
BLS12-381 signatures. At the moment, there are no practical implementations
of BLS signatures that (1) have been audited, (2) could be compiled into WebAssembly,
and (3) would fit into the gas limit.

Hence, verification of BLS signatures
in the Rainbow Bridge is currently performed off-chain, which makes it not trustless,
as we would like. This proposal adds the ability
to verify the aggregated BLS12-381 signature to the NEAR Runtime level.
This NEP makes verifing ETH2 data completely on NEAR possible, which makes the Rainbow Bridge
trustless.

## Rationale and alternatives

One of the core parts of Rainbow Bridge is the Ethereum Light client implemented on NEAR.
The ETH2 Light Client has to verify the Light Client Update and, as a part of that,
the BLS signature.

The main alternative is to use an existing BLS verification library inside a smart contract,
which requires no changes to NEAR.

Following Rust libraries for the BLS signature verification are available.

* [BLST](https://github.com/supranational/blst) (used by Lighthouse by default), Rust bindings to C-library implementation by Supranational team. Has some audits, some are in progress.
Has the best performance, but cannot be compiled to WebAssembly and cannot be embedded into a smart contract.
* [sigp/milagro-bls](https://github.com/sigp/milagro_bls). Implemented by SigmaPrime, uses The Apache Milagro Cryptographic Library. Optionally could be enabled in LightHouse. Not Audited.
The BLS signature verification is very gas-consuming. In our tests it has exceeded the NEAR per-contract function gas limit
dozens of times over.
* [filecoin/bls-signatures](https://github.com/filecoin-project/bls-signatures). Implementation of BLS signatures in pure Rust. Not Audited.
* [zkcrypto/bls12-381](https://github.com/zkcrypto/bls12_381) (zk-crypto implementation. By ZCash developers) Not Audited.

Alternatively, one could consider trying to split the signature verification function into multiple iterations,
or developing a faster, smart contract-compatible implementation on our own.
However, such development require low-level changes in the bls-verification libraries and long-term audit in order
to comply with all security guidelines.

The other alternative is execution signature verification off-chain using multiple validators and multisigs.
This solution currently implement in the Rainbow Bridge.
However, in this case, Rainbow Bridge can't be considered as trustless.

This NEP proposes to make the BLS-signature verification as NEAR runtime pre-compiled function.
In that case we can use the fast and audited BLST library.
Our calculation shows that the per-contract function gas limit is not exceeded in this case.

## Specification

This NEP introduces the following host function:

```rust
extern "C" {
/// Verify the given BLS12-381 signature for the given
/// message and either a non-empty public keys list or an aggregated public key.
///
/// A correct signature is a sequence of 96 bytes.
///
/// The message is a possibly empty sequence of arbitrary bytes.
///
/// If a list of public keys is passed, all keys should be written consecutively
/// without any separation. Each should is a sequence of 48 bytes, hence
/// a list of N keys should be 48*N bytes long.
///
/// If an aggregated public key is used instead, it should be passed alone.
/// The length of an aggregated public key is always 48 bytes as well.
///
/// Returns 1 if the signature verification pass, and 0 if the underlying
/// `BLST` library returns `BLST_VERIFY_FAIL`.
///
/// # Errors
///
/// Contract execution is terminated with an error if either:
///
/// * Any passed arrays are out of memory bounds
/// * The underlying library returns any error except `BLST_VERIFY_FAIL`.
/// E.g. the signature is invalid regardless of the message.
///
/// # Cost
///
/// `input_cost(aggregate_signature_len) + input_cost(msg_len) + input_cost(pubkeys_len) +
/// bls12381_verify_base + bls12381_verify_byte * msg_len + bls12381_verify_elements * pubkeys_cnt`
///
/// Here `pubkeys_cnt` is the number of public keys (`pubkeys_len / 48`).
///
/// BLS12-381 allows aggregating all public keys provided into a single key equivalent to the list.
/// That way the number of public keys and the message length influence the cost independently.
fn bls12_381_aggregate_verify(
aggregate_signature_ptr: u64,
aggregate_signature_len: u64,
msg_ptr: u64,
msg_len: u64,
pubkeys_ptr: u64,
pubkeys_len: u64,
) -> u64;
}
```

A `rust-sdk` possible implementation could look like this:

```rs
pub fn bls12_381_aggregate_verify(aggregate_signature: &[u8], msg: &[u8], pubkeys: &[u8]) -> u64;
```

Once this NEP is approved and integrated, these functions will be available in the `near_sdk` crate in the
`env` module.

The main goal is to be able to check the sync committee signature of light client update for Ethereum 2.
For example:
```rust
near_sdk::env::bls12_381_aggregate_verify(
&light_client_update
.sync_aggregate
.sync_committee_signature.0,
&signing_root.0.as_bytes(),
&pubkeys
);
```
Here `signing_root` is the tree hash of `light_client_update.attested_beacon_header` and `pubkeys`
is the concatenation of the subset of `sync_committee.pubkeys`.
Specifically, only pubkeys which signed light client update.
In other words, pubkeys with a corresponding bit in `light_client_update.sync_aggregate.sync_committee_bits` set to 1.

For verification inside `bls12_381_aggregate_verify` function [the BLST library](https://github.com/supranational/blst) is used,
specifically the `fast_aggregate_verify`. The PR with implementation is available at https://github.com/near/nearcore/pull/8184

## Security Implications (Optional)

The security of a function depends on the security of the library used.
We suggest using [the BLST library](https://github.com/supranational/blst). This library were audited and shows the good performance.

## Unresolved issues

1. The verification can fail in two ways: either err or return 0.
The distinction between the two is unclear, and the goal of such distinction is unclear as well.
BLST library has lots of possible verification fail causes, and we call it multiple times, which should be what?
2. It may be useful to aggregate a list of public keys once and use it later for multiple verifications.
This improves performance.
Maybe add a separate aggregation function and make the current function accept only a single key (either usual or aggregated).

## Future possibilities

The Ethereum 2 supports even more low-level precompiled operations for BLS12-381,
see [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537).
In the future it can be useful to support these operations on NEAR as well to be able
effectively execute Ethereum 2 contracts on NEAR.

## Copyright

[copyright]: #copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).