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, there are implementations for both the Signer
and Verifier
traits from the signature crate.
use eyre::Result;
use karak_kms::keypair::{
bn254::{bls::signature::Signature, Keypair as Bn254Keypair},
traits::Keypair,
};
use signature::Signer;
fn sign_using_bn254_keypair() -> Result<Signature> {
// generate a random keypair for the BN254 curve
let keypair = Bn254Keypair::generate();
// random message
let message = b"Hello, world!";
// sign message using the keypair
Ok(keypair.try_sign(message)?)
}
Verify message signed by BN254 keypair
To verify the validity of a signature you can use the verify
method of the Verifier
trait implementation on the G2Pubkey
. The method takes in a message
and signature
.
use eyre::Result;
use karak_kms::keypair::{bn254::Keypair as Bn254Keypair, traits::Keypair};
use signature::{Keypair as SignatureKeypair, Signer, Verifier};
fn verify_message_signed_by_bn254_keypair() -> Result<()> {
// generate a random keypair for the BN254 curve
let keypair = Bn254Keypair::generate();
// random message
let message = b"Hello, world!";
// sign message using the keypair
let signature = keypair.try_sign(message)?;
// extract the g2 pubkey from the keypair
let g2_pubkey = keypair.public_key().g2;
Ok(g2_pubkey.verify(&message, &signature)?)
// alternatively, you could use the verifying_key method from the signature::Keypair trait to get the g2 pubkey and then verify
// Ok(keypair.verifying_key().verify(&message, &signature)?)
}
Verify signature using both G1 and G2 pubkeys
We also provide an implementation of the Verifier
trait on the combined pubkey, {g1: G1Pubkey, g2: G2Pubkey}
.
use eyre::Result;
use karak_kms::keypair::{bn254::Keypair as Bn254Keypair, traits::Keypair as _};
use signature::{Signer, Verifier};
fn verify_message_signed_by_bn254_keypair() -> Result<()> {
// generate a random keypair for the BN254 curve
let keypair = Bn254Keypair::generate();
// random message
let message = b"Hello, world!";
// sign message using the keypair
let signature = keypair.try_sign(message)?;
// extract the public key (containing both g1 & g2 pubkeys)
let pubkey = keypair.public_key();
// verify the signature
Ok(pubkey.verify(&message, &signature)?)
}
Note: using the G2Pubkey
is what you usually want to use for verifying BLS signatures, since signatures are nothing but G1Point
s themselves. The combined pubkey is useful only when you want to additionally verify that the 2 pubkeys are generated from the same private key.
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::signers::local::PrivateKeySigner;
use alloy::{
network::EthereumWallet,
primitives::{Address, U256},
providers::ProviderBuilder,
};
use eyre::Result;
use karak_rs::{
contracts::Core,
kms::keypair::bn254::bls::{
registration::{BlsRegistration, OperatorRegistration},
signature::Signature,
},
kms::keypair::bn254::{G1Pubkey, G2Pubkey},
};
use std::str::FromStr;
async fn register_operator() -> Result<()> {
// address of the DSS contract
let dss_address = Address::from_str("ADDRESS_OF_DSS_CONTRACT>")?;
// 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 registration = BlsRegistration {
g1_pubkey,
g2_pubkey,
signature,
};
// setting up core instance
let signer: PrivateKeySigner = "<PRIVATE_KEY>".parse()?;
let wallet = EthereumWallet::from(signer.clone());
let rpc_url = "<RPC_URL>".parse()?;
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
.on_http(rpc_url);
let core = Core::new(Address::from_str("<Address_Of_Core>")?, provider);
// calling registration
core.register_operator_to_dss_with_bls(dss_address, ®istration)
.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
.
use karak_rs::kms::keypair::bn254::{bls::signature::Signature, G2Pubkey};
use signature::Verifier;
fn aggregated_sign_verification(
pubkey1: G2Pubkey,
sign1: Signature,
pubkey2: G2Pubkey,
sign2: Signature,
message: &[u8],
) {
let agg_pubkey = pubkey1 + pubkey2;
let agg_sign = sign1 + sign2;
assert!(agg_pubkey.verify(message, &agg_sign).is_ok());
}
Conversion to alloy types
As you can see in the registration example above, the data types used in the BLS library can be converted to and from tuples of the U256
alloy type. This is done to make the data types compatible with the onchain BLS library.
let keypair = karak_rs::kms::keypair::bn254::Keypair::generate();
let g1 = keypair.public_key().g1;
let g2 = keypair.public_key().g2;
let (g1_x, g1_y) = <(U256, U256)>::from(g1);
let ([g2_x1, g2_x0], [g2_y1, g2_y0]) = <([U256; 2], [U256; 2])>::from(g2);
assert_eq!(g1, G1Pubkey::from((g1_x, g1_y)));
assert_eq!(g2, G2Pubkey::from(([g2_x1, g2_x0], [g2_y1, g2_y0])));
Note that the conversion for a G2Point
has the order reversed in the components for both x
and y
coordinates.
Additionally, these types implement the SolValue
trait, so you can directly call .abi_encode()
on them to get the ABI encoded bytes.
let keypair = karak_rs::kms::keypair::bn254::Keypair::generate();
let g1 = keypair.public_key().g1;
let g2 = keypair.public_key().g2;
(g1, g2).abi_encode();