Skip to content

Commit

Permalink
Support BLS Aggregate Signatures in FVM (FIP-0079) (#841)
Browse files Browse the repository at this point in the history
  • Loading branch information
DrPeterVanNostrand authored Oct 19, 2023
1 parent 85023a2 commit 6b54250
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 0 deletions.
249 changes: 249 additions & 0 deletions FIPS/fip-0079.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
---
fip: "FIP-0079"
title: Add BLS Aggregate Signatures to FVM
author: "Jake (@drpetervannostrand)"
discussions-to: https://github.com/filecoin-project/FIPs/discussions/840
status: draft
type: Technical (Core)
category: Core
created: 2023-09-27
---

<!--You can leave these HTML comments in your merged FIP and delete the visible duplicate text guides, they will not appear and may be helpful to refer to if you edit it again. This is the suggested template for new FIPs. Note that a FIP number will be assigned by an editor. When opening a pull request to submit your FIP, please use an abbreviated title in the filename, `fip-draft_title_abbrev.md`. The title should be 44 characters or less.-->

## Simple Summary
<!--"If you can't explain it simply, you don't understand it well enough." Provide a simplified and layman-accessible explanation of the FIP.-->

This FIP proposes the following changes to FVM:

1. Addition of a syscall for BLS aggregate signature verification (by definition, this also supports non-aggregate BLS signatures).
1. Removal of the syscall currently used for generic signature (i.e. Secp256k1 and non-aggregate BLS) validation, and refactoring of its associated SDK function in terms of existing Secp256k1 signature syscalls and the added aggregate BLS syscall.

## Abstract
<!--A short (~200 word) description of the technical issue being addressed.-->

Non-aggregate signature schemes allow one signer to sign one message, whereas aggregate signatures allow multiple parties to sign multiple messages via a single signature. Currently, FVM supports non-aggregate signature verification for Secp256k1 (ECDSA) and BLS (using curve BLS12-381) schemes. This FIP proposes adding support for BLS aggregate signature validation to FVM.

## Change Motivation
<!--The motivation is critical for FIPs that want to change the Filecoin protocol. It should clearly explain why the existing protocol specification is inadequate to address the problem that the FIP solves. FIP submissions without sufficient motivation may be rejected outright.-->

Currenty, FVM exclusively supports non-aggregate signatures via its `verify_signature` syscall. The motivation for this FIP is for FVM to additionally support aggregate signatures. The benefits of BLS aggregate signatures are that they allow multiple parties to sign multiple messages via a single constant-sized signature (i.e. the size of an aggregate signature is the same as a non-aggregate) and that verification of an aggregate signature is more efficient than verification of each aggregated signature individually.

## Specification
<!--The technical specification should describe the syntax and semantics of any new feature. The specification should be detailed enough to allow competing, interoperable implementations for any of the current Filecoin implementations. -->

### Interfaces

This FIP proposes the following changes to FVM's syscalls:

- Addition of a syscall `verify_bls_aggregate` for verifying a BLS aggregate signature; because the verification algorithm for non-aggregate BLS signatures is identical to that for aggregations of one signature, `verify_bls_aggregate` supports both aggregate and non-aggregate verification.

The syscall's low-level FFI API is:

```rust
/// # Arguments
///
/// - `num_signers` - the number of signatures aggregated; for non-aggregate signatures `num_signers = 1`.
/// - `sig_off` - a pointer to the first byte of the signature being verified.
/// - `pub_keys_off` - a pointer to the first public key in an array containing each signer's public
/// key.
/// - `plaintexts_off` - a pointer to the first byte in an array containing each plaintext's bytes,
/// i.e. plaintexts must be allocated contiguously in memory (equivalent to the memory layout of an
/// array containing the concatenated plaintexts' bytes).
/// - `plaintext_lens_off` - a pointer to the first element of an array containing each plaintext's
/// byte length (each plaintexrt's byte length is represented as a `u32`).
///
/// # Returns
///
/// - `Err(IllegalArgument)`:
/// - any pointer and byte length pair are an invalid location in their Wasm module's memory.
///
/// - `Ok(-1)`:
/// - the signature is an invalid G2 compressed curve point.
/// - any public key is an invalid G1 compressed curve point.
/// - there are duplicate plaintexts.
/// - any public key in is the G1 identity/zero point.
/// - the signature is invalid for the provided public keys and plaintexts.
///
/// - `Ok(0)`:
/// - the provided signature is valid for the provided signers' public keys and plaintexts.
/// - the number of signers is zero
///
pub fn verify_bls_aggregate(
num_signers: u32,
sig_off: *const u8,
pub_keys_off: *const [u8; BLS_PUB_LEN],
plaintexts_off: *const u8,
plaintext_lens_off: *const u32,
) -> Result<i32>;
```

The syscall's high-level SDK API is:

```rust
/// # Arguments
///
/// - `sig` - the BLS signature (aggregate or non-aggregate) to be verified.
/// - `pub_keys` - the signers' BLS public keys.
/// - `plaintexts` - the signed plaintexts.
///
/// # Returns
///
/// - `SyscallResult::Err(IllegalArgument)`:
/// - unequal numbers of public keys and plaintexts.
/// - plaintexts are large enough to not fit within a Wasm module instance's memory.
///
/// - `Ok(false)`:
/// - the signature is an invalid G2 compressed curve point.
/// - any public key is an invalid G1 compressed curve point.
/// - there are duplicate plaintexts.
/// - any public key in is the G1 identity/zero point.
/// - the signature is invalid for the provided public keys and plaintexts.
///
/// - `Ok(true)`:
/// - the signature is valid for the provided public keys and plaintexts.
/// - `pub_keys` and `plaintexts` are empty.
///
fn verify_bls_aggregate(
sig: &[u8; 96],
pub_keys: &[[u8; 48]],
plaintexts: &[&[u8]],
) -> SyscallResult<bool>;
```

- Removal of the syscall `verify_signature`, used for generic signature (i.e. Secp256k1 and non-aggregate BLS) verification.

### Actor changes

Due to the addition of a syscall in this FIP, a corresponding FVM SDK function `verify_bls_aggregate` is added to expose the new syscall. The SDK function's API is the same as that of the syscall:

```rust
fn verify_bls_aggregate(
aggregate_signature: &[u8; 96],
pub_keys: &[[u8; 48]],
plaintexts: &[&[u8]],
) -> bool;
```

Due to this FIP's removal of the `verify_signature` syscall, the removed syscall's corresponding FVM SDK function `verify_signature` is refactored in terms of two existing Secp256k1 syscalls (`hash` and `recover_secp_public_key`) and the new aggregate BLS syscall (`verify_bls_aggregate`). Note that this refactor does not change the SDK function's current API, and consequently, the `verify_signature` SDK function is used only for non-aggregate signature.

```rust
// Signature and signer's address (i.e. signing public key) may be either Secp256k1 or BLS.
fn verify_signature(
signature: &Signature,
signer: &Address,
plaintext: &[u8],
) -> bool;
```

### Gas Pricing

The gas pricing for `verify_bls_aggregate` is dependent upon the number of signatures aggregated and the total size (in bytes) of the signed plaintexts. The number of signatures aggregated determines the number of elliptic curve pairing operations performed by the signature verifier, and the total size of plaintexts determines the amount of hashing that a verifier must perform when mapping each plaintext to a curve point.

The gas cost of BLS aggregate signature verification is computed via:

```rust
const BLS_GAS_PER_PLAINTEXT_BYTE: usize = 26;
// The gas required to compute one elliptic curve pairing operation for curve BLS12-381.
const BLS_GAS_PER_PAIRING: usize = 8299302;

fn verify_bls_aggregate_gas(plaintexts: &[&[u8]]) -> usize {
let total_plaintext_len = plaintexts.iter().map(|plaintext| plaintext.len()).sum();
// A BLS verifier must perform one pairing operation per signature aggregated and an additional
// pairing to map the aggregate signature to the group `Gt` of curve BLS12-381.
let num_pairings = plaintexts.len() + 1;
BLS_GAS_PER_PLAINTEXT_BYTE * total_plaintext_len + BLS_GAS_PER_PAIRING * num_pairings
}
```

NOTE: The above gas numbers were estimated from existing gas charges and were not re-benchmarked.

## Design Rationale
<!--The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages. The rationale may also provide evidence of consensus within the community, and should discuss important objections or concerns raised during discussion.-->

The motivation for using BLS aggregate signatures over an alternative aggregate signature scheme is
that (non-aggregate) BLS are already supported in FVM, the changes required in FVM to support
aggregate BLS are minimal, and BLS is a widely used and well audited signature scheme. Additionally, Filecoin's BLS signatures library [`bls-signatures`](https://github.com/filecoin-project/bls-signatures) was [audited](https://research.nccgroup.com/2020/10/21/public-report-filecoin-bellman-and-bls-signatures-cryptographic-review/) by the [NCC Group](https://www.nccgroup.com) prior to Filecoin's Mainnet launch.

## Backwards Compatibility
<!--All FIPs that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity. The FIP must explain how the author proposes to deal with these incompatibilities. FIP submissions without a sufficient backwards compatibility treatise may be rejected outright.-->

As this FIP proposes the removal of a syscall from FVM, the changes are internally breaking (with respect to FVM), however as all current user-facing interfaces are unchanged; from the perspective of FVM users, the changes are non-breaking and additive (in that one additional syscall and SDK function are added to FVM).

## Test Cases
<!--Test cases for an implementation are mandatory for FIPs that are affecting consensus changes. Other FIPs can choose to include links to test cases if applicable.-->

The core implementation of FVM's BLS aggregate signature verification is tested in [`ref-fvm/shared/src/crypto/signature.rs`](https://github.com/filecoin-project/ref-fvm/blob/master/shared/src/crypto/signature.rs) and the syscall SDK in FVM's [syscall actor integration tests](https://github.com/filecoin-project/ref-fvm/blob/master/testing/test_actors/actors/fil-syscall-actor/src/actor.rs).

## Security Considerations
<!--All FIPs must contain a section that discusses the security implications/considerations relevant to the proposed change. Include information that might be important for security discussions, surfaces risks and can be used throughout the life cycle of the proposal. E.g. include security-relevant design decisions, concerns, important discussions, implementation-specific guidance and pitfalls, an outline of threats and risks and how they are being addressed. FIP submissions missing the "Security Considerations" section will be rejected. A FIP cannot proceed to status "Final" without a Security Considerations discussion deemed sufficient by the reviewers.-->

As FVM currently supports non-aggregate BLS signatures, this FIP does not affect FVM security.

One security consideration in using BLS aggregate signatures is that plaintexts signed by a single aggregate signature are required to be unique (i.e. multiple BLS private keys cannot sign the same plaintext, then proceed to aggregate those signatures).

## Product Considerations
<!--All FIPs must contain a section that discusses the product implications/considerations relative to the proposed change. Include information that might be important for product discussion. A discussion on how the proposed change will enable better storage-related goods and services to be developed on Filecoin. FIP submissions missing the "Product Considerations" section will be rejected. An FIP cannot proceed to status "Final" without a Product Considerations discussion deemed sufficient by the reviewers.-->

This FIP affects the product in the following ways:

1. It paves the way to allow more general BLS signature validation in smart contracts.
1. It makes the FVM less Filecoin specific.

## Implementation
<!--The implementations must be completed before any core FIP is given status "Final", but it need not be completed before the FIP is accepted. While there is merit to the approach of reaching consensus on the specification and rationale before writing code, the principle of "rough consensus and running code" is still useful when it comes to resolving many discussions of API details.-->

Using Rust-like pseudocode, the core implementation of FVM's BLS aggregate signature verification is:

```rust
fn verify_bls_aggregate(
aggregate_signature: &[u8; 96],
pub_keys: &[[u8; 48]],
plaintexts: &[&[u8]],
) -> bool {
if pub_keys.len() != plaintexts.len() {
return false;
}

// Enforce uniqueness of the signed plaintexts.
for (i, plaintext) in plaintexts.iter().take(plaintexts.len() - 1).enumerate() {
for other_plaintext in &plaintexts[i + 1..] {
if plaintext == other_plaintext {
return false;
}
}
}

// Deserialize public keys and aggregate signature bytes into BLS12-381 G1 and G2 points.
let pub_keys: Vec<G1> = pub_keys.iter().map(G1::from_bytes).collect();
let aggregate_sig = G2::from_bytes(aggregate_signature);

// Enforce that no public key is the G1 identity/zero point of curve BLS12-381.
if pub_keys.iter().any(|pub_key| pub_key == G1::zero()) {
return false;
}

// Hash each signed plaintext to a G2 point of curve BLS12-381.
let digests: Vec<G2> = plaintexts.iter().map(|msg| hash_to_g2(msg)).collect();

// Verify the aggregate signature by checking an equality of pairings (`e`):
// `e(G1::generator(), aggregate_signature) == e(pub_key_1, digest_1) * ... * e(pub_key_n, digest_n)`.
let mut miller_loop: Fp12 = pub_keys
.iter()
.zip(&digests)
.map(|(pub_key, digest)| miller_loop(pub_key, digest))
.product();
miller_loop *= miller_loop(-G1::generator(), aggregate_signature);
miller_loop.final_exponentiation() == Gt::one()
}
```

FVM uses the Rust crates [`bls-signatures`](https://github.com/filecoin-project/bls-signatures) for making and verifying BLS signatures, and [`blstrs`](https://github.com/filecoin-project/blstrs) for functionality specific to elliptic curve BLS12-381, which FVM uses for BLS signatures.

## TODO

1. Benchmark gas pricing.

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

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,5 @@ This improvement protocol helps achieve that objective for all members of the Fi
|[0076](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0076.md) | Direct data onboarding | FIP | @anorth, @zenground0 | Draft |
|[0077](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0077.md) | Add Cost Opportunity For New Miner Creation | FIP |Zac (@remakeZK), Mike Li (@hunjixin)| Draft |
|[0078](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0078.md) | Remove Restrictions on the Minting of Datacap | FIP |Fatman13 (@Fatman13), flyworker (@flyworker), stuberman (@stuberman), Eliovp (@Eliovp), dcasem (@dcasem), and The-Wayvy (@The-Wayvy)| Draft |
|[0079](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0079.md) | Add BLS Aggregate Signatures to FVM | FIP | Jake (@drpetervannostrand) | Draft |

0 comments on commit 6b54250

Please sign in to comment.