Skip to content

BLS Signature Scheme

The BLS (Boneh–Lynn–Shacham) library offers a simple way to integrate BLS signature schemes into your DSS. BLS allows for efficient signature aggregation and multi-signature verification. By enabling the aggregation of multiple signatures into a single compact signature, the library reduces on-chain data size and computational overhead, making it ideal for use in threshold signatures and consensus algorithms.

This crate includes a complementary on-chain BLS library designed to interact seamlessly with it. The crate provides bindings and types that work directly with the on-chain BLS library, And also supports algebraic operations on BN254 curve points.

Note: 1. This crate operates on the curve BN254
2. The crate uses Ark_BN254 for low level elliptic curve algebra.
3. This crate operates upon the BN254 Keypair
4. Operations within the crate follow little endian format. Whereas in the ETH ecosystem operations are in big endian

Signing using BN254 keypair

For signing with the BN254 keypair, The SDK has a KeypairSigner that takes in the BN254 keypair, and can be used to sign messages of length 32 Bytes.

use eyre::Result;
use karak_rs::{
    bls::{keypair_signer::KeypairSigner, signature::Signature},
    kms::{
        keypair::{bn254::Keypair, traits::Keypair as KeypairTrait},
        signer::traits::Signer,
    },
};
 
fn sign_using_bn254_keypair() -> Result<Signature> {
    // generate a random keypair for the BN254 curve
    let keypair = keypair::generate();
    
    //generate a KeypairSigner from the BN254 keypair
    let keypair_signer = KeypairSigner::from(keypair.clone());
 
    // random message: that has to be of length 32
    let message = [42u8; 32];
 
    // sign message using the signer
    Ok(keypair_signer.sign_message(message).unwrap())
}

Verify message signed by BN254 keypair

To verify the validity of a signature you can use the verify_signature method. The method takes in a mesassage, signature and Pubkey.

use eyre::Result;
use karak_rs::{
    bls::keypair_signer::{verify_signature, KeypairSigner},
    kms::{
        keypair::{bn254::Keypair, traits::Keypair as KeypairTrait},
        signer::traits::Signer,
    },
};
 
fn verify_message_signed_by_bn254_keypair() -> Result<bool> {
    // generate a random keypair for the BN254 curve
    let keypair = Keypair::generate(); 
 
    //generate a KeypairSigner from the BN254 keypair
    let keypair_signer = KeypairSigner::from(keypair.clone());
 
    // random message: that has to be of length 32
    let message = [42u8; 32];
 
    let signature = keypair_signer.sign_message(message).unwrap();
 
Ok(verify_signature(&keypair.public_key().g2, &signature, message).is_ok())
}

Hash to G1 point

For signing a message in BLS, A conversion for the message hash to a point on the G1 group in necessary. The conversion can be done using the hash_to_g1_point method. This method takes a message of length 32 as the input and returns a G1 point.

use ark_bn254::G1Affine;
use eyre::Result;
use karak_rs::bls::keypair_signer::hash_to_g1_point;
use std::borrow::Borrow;
 
fn convert_hash_to_g1() -> G1Affine {
    // random message: that has to be of length 32
    let message = [42u8; 32];
 
    Ok(hash_to_g1_point(message.borrow()))
}

G1Affine is a type provided by the Ark_BN254

Register to onchain BLS library

The onchain library requires an operator address and a bytes group containing the compressed data. This crate offers the register_operator_to_dss_with_data method, which transforms the provided data into the required format and submits it via a transaction to the contract utilizing the onchain BLS library.

 
use alloy::{
    network::EthereumWallet,
    primitives::{keccak256, Address, U256},
    providers::ProviderBuilder,
};
use karak_rs::{
    bls::{
        registration::{BlsRegistration, OperatorRegistration},
        signature::Signature,
    },
    contracts::Core,
    kms::keypair::bn254::{G1Pubkey, G2Pubkey},
};
use eyre::Result;
use std::str::FromStr;
use alloy::signers::local::PrivateKeySigner;
 
async fn register_operator() -> Result<()>{
    // address of the DSS contract
    let dss_address = Address::from_str("ADDRESS_OF_DSS_CONTRACT>").unwrap();
 
    // importing points
    let g1_x =
        U256::from_str("0xb77a215b4f5cdd99f5ba438cc4996175bc729449b2cb250c3fb74eed2aaee62")?;
    let g1_y =
        U256::from_str("0x2158bc12a3fd6ab600d7a48a8b99a5defb35ac5a5bf652e368aeda7b97234cb6")?;
    let g2_x0 =
        U256::from_str("0x13de1b6acc2713a59bb7b57b71dcc47bb51e92bb5ce2ea27c22545b09ceb9e9")?;
    let g2_x1 =
        U256::from_str("0x80ce0e41fd5f198b867e8541cbc00b059d6e93e3611c2e2d3b55a7f2b519265")?;
    let g2_y0 =
        U256::from_str("0x2c2970386ad634cd092b2fb6308af892adee32057f139b3a7b3c3fe9559ded4e")?;
    let g2_y1 =
        U256::from_str("0x1873fc36edfb182034b6e1bbe05375c36f5bbbc39903fb431185787f28cb533d")?;
    let signature_x =
        U256::from_str("0x1294534ba6b2dc743ea1e8050e9e62526b7cb6d094ac5383bdd496568b1be0ba")?;
    let signature_y =
        U256::from_str("0x220338c1a0124231fa2ed8b56305a670b0c1f2d2ab43ceb6f69c3c5f56e109cf")?;
 
    // converting imported points to desired format
    let g1_pubkey = G1Pubkey::from((g1_x, g1_y));
    let g2_pubkey = G2Pubkey::from(([g2_x1, g2_x0], [g2_y1, g2_y0]));
    let signature = Signature::from((signature_x, signature_y));
    let msg_hash = keccak256(b"test message");
 
    let registration = BlsRegistration {
        g1_pubkey,
        g2_pubkey,
        signature,
        msg_hash,
    };
 
    // setting up core instance
    let signer: PrivateKeySigner = "<PRIVATE_KEY>".parse().expect("should parse private key");
    let wallet = EthereumWallet::from(signer.clone());
    let rpc_url = "<RPC_URL>".parse().expect("should parse rpc_url");
    let provider = ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(rpc_url);
    let core = Core::new(Address::from_str("<Address_Of_Core>").unwrap(), provider);
 
    // calling registration
    core.register_operator_to_dss_with_bls(dss_address, &registration).await?;
    Ok(())
}

Data Types

All the following data types are fully compatible with the onchain BLS library. They can be sent directly to the contracts, with any necessary type conversions handled automatically in the background.

G1 Point

G1 point represents an elliptic curve point on the G1 group. This data type is a wrapper on top of G1Affine defined by Ark_BN254

pub struct G1Point(pub G1Affine);

G2 Point

G2 point represents an elliptic curve point on the G2 group. This data type is a wrapper on top of G2Affine defined by Ark_BN254

pub struct G2Point(pub G2Affine);

Signature

A signature is another point on the G1 curve, created by converting a message into a G1 point and then performing scalar multiplication with the private_key which is a scalar field element.

BN254 point algebra

The Karak SDK supports algebraic operations on G1 and G2 curve points, such as aggregation, subtraction, and negation.

Aggregation

Aggregation can be performed on both G1 and G2 points, allowing multiple keys or signatures to be combined into a single aggregated value. This is particularly useful for use cases like aggregating public keys or signatures in distributed systems.

fn aggregate_points(point_one: G1Point, point_two: G1Point) -> G1Point {
    return point_one + point_two;
}

Negation

Negation is the operation that computes the inverse of a point and is applicable to both G1 and G2 points.

fn negate_point(point: G1Point) -> G1Point {
    return -point;
}

Subtraction

Subtraction is available for both G1 and G2 points. Under the hood, it negates the second point and then adds it to the first, effectively computing the difference between two points.

fn subtract_points(point_one: G2Point, point_two: G2Point) -> G2Point {
    return point_one - point_two;
}

Sum

Both G1 and G2 point implement the Sum trait that enables operations like this

let keys = (0..10)
    .map(|_| karak_rs::kms::keypair::bn254::Keypair::generate())
    .collect::<Vec<_>>();
 
let agg_key = keys.iter().map(|k| k.public_key().g2).sum::<G2Point>();
println!("Aggregated key: {agg_key}");

Aggregated Signature Verification

To aggregate signatures from multiple operators, each signs the same message. The aggregated public key on the G2 curve is then used to verify the aggregated signature on the G1 curve through elliptic curve pairings, ensuring that all the operators did in fact sign that particular message.


               | G2 Point    | G1 Point    
    Operator 1 | PublicKey 1 | Signature 1
+   Operator 2 | PublicKey 2 | Signature 2
+   Operator 3 | PublicKey 3 | Signature 3
    --------------------------------------
    Aggregated | AggPubkey   | AggSign

Like each individual public key can verify its corresponding signature similarly, the aggregated public key AggPubkey can be used to verify the aggregated signature AggSign.

fn aggregated_sign_verification(pubkey1: G2Point, sign1: G1Point, pubkey2: G2Point, sign2: G1Point, message: [u8; 32]) {
    let agg_pubkey = pubkey1 + pubkey2;
    let agg_sign = sign1 + sign2;
    assert!(verify_signature(&agg_pubkey, &agg_sign, message).is_ok());
}