BLS Signatures in Java: Complete Implementation Guide

Introduction to BLS Signatures

BLS (Boneh-Lynn-Shacham) signatures are a cryptographic signature scheme that enables signature aggregation and verification. They are widely used in blockchain applications, consensus protocols, and distributed systems due to their unique properties:

  • Signature Aggregation: Multiple signatures can be combined into a single signature
  • Key Aggregation: Multiple public keys can be combined
  • Deterministic: Same message always produces the same signature
  • Short Signatures: Signatures are very compact
  • Threshold Signatures: Enable m-of-n signing schemes

Architecture Overview

graph TB
subgraph "BLS Core Components"
SK[Secret Key]
PK[Public Key]
SIG[Signature]
VER[Verification]
AGG[Aggregation]
end
subgraph "Elliptic Curve Operations"
G1[G1 Group - Signatures]
G2[G2 Group - Public Keys]
GT[GT Group - Pairings]
end
subgraph "Applications"
TH[Threshold Signatures]
MU[Multi-Signature]
AG[Aggregate Signatures]
end
SK --> PK
PK --> VER
SK --> SIG
SIG --> VER
SIG --> AGG
AGG --> AG
AGG --> MU
AGG --> TH
style SK fill:#f9f,stroke:#333,stroke-width:2px
style PK fill:#bbf,stroke:#333,stroke-width:2px
style SIG fill:#bfb,stroke:#333,stroke-width:2px

Core Dependencies

<properties>
<bouncycastle.version>1.77</bouncycastle.version>
<milagro.version>3.0.0</milagro.version>
<herumi.version>1.0.0</herumi.version>
</properties>
<dependencies>
<!-- Bouncy Castle for cryptography -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<!-- Milagro for BLS implementation -->
<dependency>
<groupId>org.apache.milagro</groupId>
<artifactId>milagro-crypto-java</artifactId>
<version>${milagro.version}</version>
</dependency>
<!-- Herumi BLS (optional) -->
<dependency>
<groupId>com.herumi</groupId>
<artifactId>bls-java</artifactId>
<version>${herumi.version}</version>
</dependency>
<!-- For SHA-256/512 hashing -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.0</version>
</dependency>
<!-- For serialization -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.25.1</version>
</dependency>
</dependencies>

Core BLS Implementation

BLS Parameters and Curve Configuration

package com.bls.crypto;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;
public class BLSParameters {
// BLS12-381 curve parameters (standard for BLS signatures)
public static final BigInteger CURVE_ORDER = new BigInteger(
"73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16
);
public static final BigInteger FIELD_MODULUS = new BigInteger(
"1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16
);
public static final String CURVE_NAME = "BLS12-381";
public static final int SECURITY_LEVEL = 128; // bits
// Domain separation tags
public static final byte[] DST_G1 = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_".getBytes();
public static final byte[] DST_G2 = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_".getBytes();
// Ciphersuite IDs
public static final String CIPHERSUITE_BLS12381G1 = 
"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
public static final String CIPHERSUITE_BLS12381G2 = 
"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
// Key lengths in bytes
public static final int SECRET_KEY_LENGTH = 32;
public static final int PUBLIC_KEY_LENGTH_G1 = 48;  // Compressed
public static final int PUBLIC_KEY_LENGTH_G2 = 96;  // Compressed
public static final int SIGNATURE_LENGTH = 96;      // Compressed G1
public static final int AGGREGATED_SIG_LENGTH = 96; // Same as signature
private BLSParameters() {
// Utility class
}
}

BLS Secret Key

package com.bls.crypto;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
import org.bouncycastle.crypto.params.HKDFParameters;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Objects;
public class BLSSecretKey {
private final BigInteger scalar;
private byte[] keyMaterial;
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
public BLSSecretKey() {
// Generate random secret key
this.scalar = generateRandomScalar();
this.keyMaterial = scalar.toByteArray();
}
public BLSSecretKey(byte[] ikm) {
// Key derivation using HKDF
this.scalar = deriveKey(ikm);
this.keyMaterial = scalar.toByteArray();
}
public BLSSecretKey(BigInteger scalar) {
if (scalar.compareTo(BigInteger.ZERO) <= 0 || 
scalar.compareTo(BLSParameters.CURVE_ORDER) >= 0) {
throw new IllegalArgumentException("Invalid scalar value");
}
this.scalar = scalar;
this.keyMaterial = scalar.toByteArray();
}
private BigInteger generateRandomScalar() {
byte[] randomBytes = new byte[BLSParameters.SECRET_KEY_LENGTH];
SECURE_RANDOM.nextBytes(randomBytes);
return deriveKey(randomBytes);
}
private BigInteger deriveKey(byte[] ikm) {
// HKDF-Extract and Expand to get valid scalar
HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest());
hkdf.init(new HKDFParameters(ikm, null, null));
byte[] okm = new byte[BLSParameters.SECRET_KEY_LENGTH];
hkdf.generateBytes(okm, 0, okm.length);
// Convert to scalar modulo curve order
BigInteger scalar = new BigInteger(1, okm);
return scalar.mod(BLSParameters.CURVE_ORDER);
}
public BLSPublicKey derivePublicKey() {
return new BLSPublicKey(this);
}
public BigInteger getScalar() {
return scalar;
}
public byte[] toBytes() {
return keyMaterial.clone();
}
public static BLSSecretKey fromBytes(byte[] bytes) {
return new BLSSecretKey(new BigInteger(1, bytes));
}
public byte[] sign(byte[] message) {
return sign(message, BLSParameters.DST_G1);
}
public byte[] sign(byte[] message, byte[] dst) {
// Hash message to G1
byte[] hash = hashToG1(message, dst);
// Multiply by secret key scalar
return scalarMult(hash, scalar);
}
private native byte[] scalarMult(byte[] point, BigInteger scalar);
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BLSSecretKey that = (BLSSecretKey) o;
return scalar.equals(that.scalar);
}
@Override
public int hashCode() {
return Objects.hash(scalar);
}
@Override
public String toString() {
return "BLSSecretKey{" +
"scalar=" + scalar.toString(16).substring(0, 16) + "...}";
}
}

BLS Public Key

package com.bls.crypto;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Objects;
public class BLSPublicKey {
private final byte[] point; // Compressed G2 point
public BLSPublicKey(BLSSecretKey secretKey) {
// g^sk where g is generator of G2
this.point = generatePublicKey(secretKey.getScalar());
}
public BLSPublicKey(byte[] compressedPoint) {
validatePublicKey(compressedPoint);
this.point = compressedPoint.clone();
}
private native byte[] generatePublicKey(BigInteger scalar);
private void validatePublicKey(byte[] compressedPoint) {
if (compressedPoint.length != BLSParameters.PUBLIC_KEY_LENGTH_G2) {
throw new IllegalArgumentException("Invalid public key length");
}
// Additional validation (ensure point is on curve, not identity, etc.)
if (!isValidPoint(compressedPoint)) {
throw new IllegalArgumentException("Invalid public key point");
}
}
private native boolean isValidPoint(byte[] compressedPoint);
public boolean verify(byte[] message, byte[] signature) {
return verify(message, signature, BLSParameters.DST_G1);
}
public boolean verify(byte[] message, byte[] signature, byte[] dst) {
// e(g, sig) == e(pk, H(msg))
return verifyPairing(point, signature, hashToG1(message, dst));
}
private native boolean verifyPairing(byte[] pk, byte[] sig, byte[] hash);
public byte[] toBytes() {
return point.clone();
}
public static BLSPublicKey fromBytes(byte[] bytes) {
return new BLSPublicKey(bytes);
}
public String toHex() {
return Hex.toHexString(point);
}
public static BLSPublicKey fromHex(String hex) {
return new BLSPublicKey(Hex.decode(hex));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BLSPublicKey that = (BLSPublicKey) o;
return Arrays.equals(point, that.point);
}
@Override
public int hashCode() {
return Arrays.hashCode(point);
}
@Override
public String toString() {
return "BLSPublicKey{" +
"point=" + Hex.toHexString(point).substring(0, 16) + "...}";
}
}

BLS Signature

package com.bls.crypto;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class BLSSignature {
private final byte[] point; // Compressed G1 point
public BLSSignature(byte[] compressedPoint) {
validateSignature(compressedPoint);
this.point = compressedPoint.clone();
}
private void validateSignature(byte[] compressedPoint) {
if (compressedPoint.length != BLSParameters.SIGNATURE_LENGTH) {
throw new IllegalArgumentException("Invalid signature length");
}
// Additional validation (ensure point is on curve, not identity, etc.)
if (!isValidPoint(compressedPoint)) {
throw new IllegalArgumentException("Invalid signature point");
}
}
private native boolean isValidPoint(byte[] compressedPoint);
public boolean verify(BLSPublicKey publicKey, byte[] message) {
return publicKey.verify(message, point);
}
public boolean verify(BLSPublicKey publicKey, byte[] message, byte[] dst) {
return publicKey.verify(message, point, dst);
}
public byte[] toBytes() {
return point.clone();
}
public static BLSSignature fromBytes(byte[] bytes) {
return new BLSSignature(bytes);
}
public String toHex() {
return Hex.toHexString(point);
}
public static BLSSignature fromHex(String hex) {
return new BLSSignature(Hex.decode(hex));
}
public BLSSignature add(BLSSignature other) {
// Add two signatures (elliptic curve point addition)
byte[] sum = pointAddition(this.point, other.point);
return new BLSSignature(sum);
}
private native byte[] pointAddition(byte[] a, byte[] b);
public static BLSSignature aggregate(List<BLSSignature> signatures) {
if (signatures == null || signatures.isEmpty()) {
throw new IllegalArgumentException("No signatures to aggregate");
}
// Start with first signature
BLSSignature result = signatures.get(0);
// Add all other signatures
for (int i = 1; i < signatures.size(); i++) {
result = result.add(signatures.get(i));
}
return result;
}
public static boolean verifyAggregate(List<BLSSignature> signatures, 
List<BLSPublicKey> publicKeys,
List<byte[]> messages) {
if (signatures.size() != publicKeys.size() || 
signatures.size() != messages.size()) {
throw new IllegalArgumentException("List sizes must match");
}
// Verify all signatures individually
for (int i = 0; i < signatures.size(); i++) {
if (!signatures.get(i).verify(publicKeys.get(i), messages.get(i))) {
return false;
}
}
return true;
}
public static boolean verifyAggregateSameMessage(BLSSignature aggregatedSig,
List<BLSPublicKey> publicKeys,
byte[] message) {
// Verify aggregated signature for same message
// e(g, sig) == e(pk1 * pk2 * ... * pkn, H(msg))
BLSPublicKey aggregatedKey = BLSPublicKey.aggregate(publicKeys);
return aggregatedKey.verify(message, aggregatedSig.toBytes());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BLSSignature that = (BLSSignature) o;
return Arrays.equals(point, that.point);
}
@Override
public int hashCode() {
return Arrays.hashCode(point);
}
@Override
public String toString() {
return "BLSSignature{" +
"point=" + Hex.toHexString(point).substring(0, 16) + "...}";
}
}

JNI Bridge for Native BLS Operations

package com.bls.nativebridge;
public class BLSJNI {
static {
// Load native library
System.loadLibrary("bls_jni");
}
// Core operations
public static native byte[] hashToG1(byte[] message, byte[] dst);
public static native byte[] hashToG2(byte[] message, byte[] dst);
// G1 operations
public static native byte[] g1Generator();
public static native byte[] g1ScalarMult(byte[] point, byte[] scalar);
public static native byte[] g1Add(byte[] a, byte[] b);
public static native boolean g1IsValid(byte[] point);
public static native byte[] g1Negate(byte[] point);
public static native byte[] g1Serialize(byte[] point);
public static native byte[] g1Deserialize(byte[] data);
// G2 operations
public static native byte[] g2Generator();
public static native byte[] g2ScalarMult(byte[] point, byte[] scalar);
public static native byte[] g2Add(byte[] a, byte[] b);
public static native boolean g2IsValid(byte[] point);
public static native byte[] g2Negate(byte[] point);
public static native byte[] g2Serialize(byte[] point);
public static native byte[] g2Deserialize(byte[] data);
// Pairing operations
public static native byte[] pairing(byte[] g1Point, byte[] g2Point);
public static native boolean verifyPairing(byte[] g1Sig, byte[] g2Pk, byte[] g1Hash);
// Key derivation
public static native byte[] deriveKey(byte[] ikm, byte[] salt, byte[] info);
// Curve parameters
public static native byte[] getCurveOrder();
public static native byte[] getFieldModulus();
}

Complete BLS Implementation with Aggregation

package com.bls.core;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
public class BLS {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
public static class KeyPair {
private final BLSSecretKey secretKey;
private final BLSPublicKey publicKey;
public KeyPair() {
this.secretKey = new BLSSecretKey();
this.publicKey = secretKey.derivePublicKey();
}
public KeyPair(byte[] seed) {
this.secretKey = new BLSSecretKey(seed);
this.publicKey = secretKey.derivePublicKey();
}
public BLSSecretKey getSecretKey() {
return secretKey;
}
public BLSPublicKey getPublicKey() {
return publicKey;
}
}
public static byte[] sign(BLSSecretKey sk, byte[] message) {
return sk.sign(message);
}
public static byte[] sign(BLSSecretKey sk, byte[] message, byte[] dst) {
return sk.sign(message, dst);
}
public static boolean verify(BLSPublicKey pk, byte[] message, byte[] signature) {
return pk.verify(message, signature);
}
public static BLSSignature aggregateSignatures(List<BLSSignature> signatures) {
return BLSSignature.aggregate(signatures);
}
public static BLSPublicKey aggregatePublicKeys(List<BLSPublicKey> publicKeys) {
return BLSPublicKey.aggregate(publicKeys);
}
public static boolean verifyAggregate(List<BLSSignature> signatures,
List<BLSPublicKey> publicKeys,
List<byte[]> messages) {
return BLSSignature.verifyAggregate(signatures, publicKeys, messages);
}
public static boolean verifyAggregateSameMessage(BLSSignature aggregatedSig,
List<BLSPublicKey> publicKeys,
byte[] message) {
return BLSSignature.verifyAggregateSameMessage(aggregatedSig, publicKeys, message);
}
public static KeyPair generateDeterministicKeyPair(byte[] seed) {
return new KeyPair(seed);
}
public static byte[] hashToG1(byte[] message) {
return hashToG1(message, BLSParameters.DST_G1);
}
public static native byte[] hashToG1(byte[] message, byte[] dst);
public static byte[] hashToG2(byte[] message) {
return hashToG2(message, BLSParameters.DST_G2);
}
public static native byte[] hashToG2(byte[] message, byte[] dst);
}

Threshold BLS Signatures

package com.bls.threshold;
import com.bls.core.*;
import java.math.BigInteger;
import java.util.*;
public class ThresholdBLS {
private final int threshold;
private final int totalParticipants;
private final Map<Integer, BLSSecretKey> participantKeys;
private final Map<Integer, BLSPublicKey> participantPublicKeys;
public ThresholdBLS(int threshold, int totalParticipants) {
if (threshold > totalParticipants) {
throw new IllegalArgumentException("Threshold cannot exceed total participants");
}
this.threshold = threshold;
this.totalParticipants = totalParticipants;
this.participantKeys = new HashMap<>();
this.participantPublicKeys = new HashMap<>();
}
public static class SharedSecret {
private final BigInteger secret;
private final List<ShamirShare> shares;
public SharedSecret(BigInteger secret, List<ShamirShare> shares) {
this.secret = secret;
this.shares = shares;
}
public BigInteger getSecret() { return secret; }
public List<ShamirShare> getShares() { return shares; }
}
public static class ShamirShare {
private final int index;
private final BigInteger value;
public ShamirShare(int index, BigInteger value) {
this.index = index;
this.value = value;
}
public int getIndex() { return index; }
public BigInteger getValue() { return value; }
}
public SharedSecret generateSharedSecret() {
BigInteger secret = new BigInteger(BLSParameters.SECRET_KEY_LENGTH * 8, 
new java.security.SecureRandom());
List<ShamirShare> shares = generateShamirShares(secret);
return new SharedSecret(secret, shares);
}
private List<ShamirShare> generateShamirShares(BigInteger secret) {
// Generate random polynomial of degree threshold-1
List<BigInteger> coefficients = new ArrayList<>();
coefficients.add(secret);
for (int i = 1; i < threshold; i++) {
coefficients.add(new BigInteger(BLSParameters.SECRET_KEY_LENGTH * 8, 
new java.security.SecureRandom()));
}
// Evaluate polynomial at points 1..totalParticipants
List<ShamirShare> shares = new ArrayList<>();
for (int x = 1; x <= totalParticipants; x++) {
BigInteger y = evaluatePolynomial(coefficients, BigInteger.valueOf(x));
shares.add(new ShamirShare(x, y));
}
return shares;
}
private BigInteger evaluatePolynomial(List<BigInteger> coefficients, BigInteger x) {
BigInteger result = BigInteger.ZERO;
BigInteger power = BigInteger.ONE;
for (BigInteger coeff : coefficients) {
result = result.add(coeff.multiply(power)).mod(BLSParameters.CURVE_ORDER);
power = power.multiply(x).mod(BLSParameters.CURVE_ORDER);
}
return result;
}
public void distributeShares(List<ShamirShare> shares) {
for (ShamirShare share : shares) {
BLSSecretKey sk = new BLSSecretKey(share.value);
participantKeys.put(share.index, sk);
participantPublicKeys.put(share.index, sk.derivePublicKey());
}
}
public byte[] signParticipant(int participantId, byte[] message) {
BLSSecretKey sk = participantKeys.get(participantId);
if (sk == null) {
throw new IllegalArgumentException("Participant not found: " + participantId);
}
return sk.sign(message);
}
public byte[] aggregateThresholdSignatures(Map<Integer, byte[]> signatures, 
byte[] message) {
if (signatures.size() < threshold) {
throw new IllegalArgumentException("Insufficient signatures: need " + 
threshold + ", have " + signatures.size());
}
// Lagrange interpolation to reconstruct master signature
List<Integer> indices = new ArrayList<>(signatures.keySet());
// Compute Lagrange coefficients for each participant
Map<Integer, BigInteger> coefficients = computeLagrangeCoefficients(indices);
// Combine signatures using coefficients
byte[] aggregated = null;
for (Map.Entry<Integer, byte[]> entry : signatures.entrySet()) {
int id = entry.getKey();
byte[] sig = entry.getValue();
// Multiply signature by Lagrange coefficient
byte[] weighted = scalarMultiplySignature(sig, coefficients.get(id));
if (aggregated == null) {
aggregated = weighted;
} else {
aggregated = addSignatures(aggregated, weighted);
}
}
return aggregated;
}
private Map<Integer, BigInteger> computeLagrangeCoefficients(List<Integer> indices) {
Map<Integer, BigInteger> coefficients = new HashMap<>();
for (int i : indices) {
BigInteger coeff = BigInteger.ONE;
BigInteger xi = BigInteger.valueOf(i);
for (int j : indices) {
if (j != i) {
BigInteger xj = BigInteger.valueOf(j);
// coeff *= (0 - xj) / (xi - xj)
BigInteger numerator = BigInteger.ZERO.subtract(xj);
BigInteger denominator = xi.subtract(xj);
BigInteger term = numerator.multiply(denominator.modInverse(
BLSParameters.CURVE_ORDER)).mod(BLSParameters.CURVE_ORDER);
coeff = coeff.multiply(term).mod(BLSParameters.CURVE_ORDER);
}
}
coefficients.put(i, coeff);
}
return coefficients;
}
private native byte[] scalarMultiplySignature(byte[] signature, BigInteger scalar);
private native byte[] addSignatures(byte[] a, byte[] b);
public BLSPublicKey getMasterPublicKey() {
// Master public key = g^secret
return aggregatePublicKeys(new ArrayList<>(participantPublicKeys.values()));
}
public boolean verifyThresholdSignature(byte[] signature, byte[] message) {
BLSPublicKey masterPK = getMasterPublicKey();
return masterPK.verify(message, signature);
}
}

Multi-Signature Scheme

package com.bls.multisig;
import com.bls.core.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class BLSMultiSignature {
private final Map<String, Signer> signers;
private final Map<String, byte[]> pendingSignatures;
public BLSMultiSignature() {
this.signers = new ConcurrentHashMap<>();
this.pendingSignatures = new ConcurrentHashMap<>();
}
public static class Signer {
private final String id;
private final BLSSecretKey secretKey;
private final BLSPublicKey publicKey;
public Signer(String id) {
this.id = id;
this.secretKey = new BLSSecretKey();
this.publicKey = secretKey.derivePublicKey();
}
public Signer(String id, byte[] seed) {
this.id = id;
this.secretKey = new BLSSecretKey(seed);
this.publicKey = secretKey.derivePublicKey();
}
public String getId() { return id; }
public BLSSecretKey getSecretKey() { return secretKey; }
public BLSPublicKey getPublicKey() { return publicKey; }
public byte[] sign(byte[] message) {
return secretKey.sign(message);
}
}
public Signer addSigner(String id) {
Signer signer = new Signer(id);
signers.put(id, signer);
return signer;
}
public Signer addSigner(String id, byte[] seed) {
Signer signer = new Signer(id, seed);
signers.put(id, signer);
return signer;
}
public void requestSignatures(String requestId, byte[] message, List<String> signerIds) {
pendingSignatures.clear();
for (String id : signerIds) {
Signer signer = signers.get(id);
if (signer != null) {
byte[] signature = signer.sign(message);
pendingSignatures.put(id, signature);
}
}
}
public MultiSignatureResult aggregateSignatures(String message) {
List<BLSSignature> signatures = new ArrayList<>();
List<BLSPublicKey> publicKeys = new ArrayList<>();
for (Map.Entry<String, byte[]> entry : pendingSignatures.entrySet()) {
signatures.add(new BLSSignature(entry.getValue()));
publicKeys.add(signers.get(entry.getKey()).getPublicKey());
}
BLSSignature aggregated = BLSSignature.aggregate(signatures);
BLSPublicKey aggregatedKey = BLSPublicKey.aggregate(publicKeys);
return new MultiSignatureResult(aggregated, aggregatedKey, signatures.size());
}
public static class MultiSignatureResult {
private final BLSSignature signature;
private final BLSPublicKey aggregatedKey;
private final int participantCount;
public MultiSignatureResult(BLSSignature signature, BLSPublicKey aggregatedKey,
int participantCount) {
this.signature = signature;
this.aggregatedKey = aggregatedKey;
this.participantCount = participantCount;
}
public BLSSignature getSignature() { return signature; }
public BLSPublicKey getAggregatedKey() { return aggregatedKey; }
public int getParticipantCount() { return participantCount; }
public boolean verify(byte[] message) {
return signature.verify(aggregatedKey, message);
}
}
public boolean verifyIndividualSignatures(byte[] message) {
for (Map.Entry<String, byte[]> entry : pendingSignatures.entrySet()) {
Signer signer = signers.get(entry.getKey());
if (!signer.getPublicKey().verify(message, entry.getValue())) {
return false;
}
}
return true;
}
public Map<String, byte[]> getPendingSignatures() {
return Collections.unmodifiableMap(pendingSignatures);
}
public void clearPending() {
pendingSignatures.clear();
}
}

Proof-of-Possession (PoP) for BLS

package com.bls.pop;
import com.bls.core.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class BLSPoP {
private static final byte[] POP_DST = "BLS_POP_BLS12381G1_XMD:SHA-256_SSWU_RO_".getBytes();
public static class PoPKeyPair {
private final BLSSecretKey secretKey;
private final BLSPublicKey publicKey;
private final byte[] proofOfPossession;
public PoPKeyPair() {
this.secretKey = new BLSSecretKey();
this.publicKey = secretKey.derivePublicKey();
this.proofOfPossession = generateProof(secretKey, publicKey);
}
public PoPKeyPair(byte[] seed) {
this.secretKey = new BLSSecretKey(seed);
this.publicKey = secretKey.derivePublicKey();
this.proofOfPossession = generateProof(secretKey, publicKey);
}
public BLSSecretKey getSecretKey() { return secretKey; }
public BLSPublicKey getPublicKey() { return publicKey; }
public byte[] getProofOfPossession() { return proofOfPossession.clone(); }
private byte[] generateProof(BLSSecretKey sk, BLSPublicKey pk) {
// Sign the public key to prove possession of secret key
byte[] message = pk.toBytes();
return sk.sign(message, POP_DST);
}
}
public static boolean verifyPoP(BLSPublicKey publicKey, byte[] proof) {
byte[] message = publicKey.toBytes();
return publicKey.verify(message, proof, POP_DST);
}
public static Set<BLSPublicKey> filterValidPoPs(Map<BLSPublicKey, byte[]> proofs) {
Set<BLSPublicKey> valid = new HashSet<>();
for (Map.Entry<BLSPublicKey, byte[]> entry : proofs.entrySet()) {
if (verifyPoP(entry.getKey(), entry.getValue())) {
valid.add(entry.getKey());
}
}
return valid;
}
public static class PoPRegistry {
private final Map<String, PoPKeyPair> registeredKeys;
private final Map<String, byte[]> proofs;
public PoPRegistry() {
this.registeredKeys = new HashMap<>();
this.proofs = new HashMap<>();
}
public PoPKeyPair register(String id) {
PoPKeyPair keyPair = new PoPKeyPair();
registeredKeys.put(id, keyPair);
proofs.put(id, keyPair.getProofOfPossession());
return keyPair;
}
public boolean verifyRegistration(String id) {
PoPKeyPair keyPair = registeredKeys.get(id);
if (keyPair == null) return false;
byte[] proof = proofs.get(id);
return verifyPoP(keyPair.getPublicKey(), proof);
}
public BLSPublicKey getPublicKey(String id) {
PoPKeyPair keyPair = registeredKeys.get(id);
return keyPair != null ? keyPair.getPublicKey() : null;
}
public List<BLSPublicKey> getAllValidPublicKeys() {
List<BLSPublicKey> valid = new ArrayList<>();
for (String id : registeredKeys.keySet()) {
if (verifyRegistration(id)) {
valid.add(registeredKeys.get(id).getPublicKey());
}
}
return valid;
}
public void revoke(String id) {
registeredKeys.remove(id);
proofs.remove(id);
}
}
}

BLS Serialization

package com.bls.serialization;
import com.bls.core.*;
import com.google.protobuf.ByteString;
import java.io.*;
public class BLSSerializer {
public static byte[] serializeKeyPair(BLS.KeyPair keyPair) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos)) {
byte[] skBytes = keyPair.getSecretKey().toBytes();
byte[] pkBytes = keyPair.getPublicKey().toBytes();
dos.writeInt(skBytes.length);
dos.write(skBytes);
dos.writeInt(pkBytes.length);
dos.write(pkBytes);
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to serialize key pair", e);
}
}
public static BLS.KeyPair deserializeKeyPair(byte[] data) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais)) {
int skLen = dis.readInt();
byte[] skBytes = new byte[skLen];
dis.readFully(skBytes);
int pkLen = dis.readInt();
byte[] pkBytes = new byte[pkLen];
dis.readFully(pkBytes);
BLSSecretKey sk = BLSSecretKey.fromBytes(skBytes);
BLSPublicKey pk = BLSPublicKey.fromBytes(pkBytes);
return new BLS.KeyPair(sk, pk);
} catch (IOException e) {
throw new RuntimeException("Failed to deserialize key pair", e);
}
}
public static class AggregatedSignature {
private final BLSSignature signature;
private final List<BLSPublicKey> publicKeys;
private final List<byte[]> messages;
public AggregatedSignature(BLSSignature signature, List<BLSPublicKey> publicKeys,
List<byte[]> messages) {
this.signature = signature;
this.publicKeys = publicKeys;
this.messages = messages;
}
public BLSSignature getSignature() { return signature; }
public List<BLSPublicKey> getPublicKeys() { return publicKeys; }
public List<byte[]> getMessages() { return messages; }
public byte[] serialize() {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos)) {
// Write signature
byte[] sigBytes = signature.toBytes();
dos.writeInt(sigBytes.length);
dos.write(sigBytes);
// Write number of participants
dos.writeInt(publicKeys.size());
// Write each public key
for (BLSPublicKey pk : publicKeys) {
byte[] pkBytes = pk.toBytes();
dos.writeInt(pkBytes.length);
dos.write(pkBytes);
}
// Write each message
for (byte[] msg : messages) {
dos.writeInt(msg.length);
dos.write(msg);
}
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to serialize aggregated signature", e);
}
}
public static AggregatedSignature deserialize(byte[] data) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais)) {
// Read signature
int sigLen = dis.readInt();
byte[] sigBytes = new byte[sigLen];
dis.readFully(sigBytes);
BLSSignature signature = BLSSignature.fromBytes(sigBytes);
// Read number of participants
int numParticipants = dis.readInt();
List<BLSPublicKey> publicKeys = new ArrayList<>();
for (int i = 0; i < numParticipants; i++) {
int pkLen = dis.readInt();
byte[] pkBytes = new byte[pkLen];
dis.readFully(pkBytes);
publicKeys.add(BLSPublicKey.fromBytes(pkBytes));
}
// Read messages
List<byte[]> messages = new ArrayList<>();
for (int i = 0; i < numParticipants; i++) {
int msgLen = dis.readInt();
byte[] msgBytes = new byte[msgLen];
dis.readFully(msgBytes);
messages.add(msgBytes);
}
return new AggregatedSignature(signature, publicKeys, messages);
} catch (IOException e) {
throw new RuntimeException("Failed to deserialize aggregated signature", e);
}
}
}
}

BLS CLI Tool

package com.bls.cli;
import com.bls.core.*;
import com.bls.threshold.ThresholdBLS;
import com.bls.multisig.BLSMultiSignature;
import com.bls.pop.BLSPoP;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.Callable;
@Command(name = "bls", 
mixinStandardHelpOptions = true, 
version = "BLS Signatures 1.0",
description = "BLS Signature CLI Tool")
public class BLSCLI {
@Command(name = "keygen", description = "Generate BLS key pair")
static class KeyGenCommand implements Callable<Integer> {
@Option(names = {"--output", "-o"}, description = "Output file")
private Path outputFile;
@Option(names = {"--seed"}, description = "Seed for deterministic key generation")
private String seed;
@Override
public Integer call() throws Exception {
BLS.KeyPair keyPair;
if (seed != null) {
keyPair = BLS.generateDeterministicKeyPair(seed.getBytes());
} else {
keyPair = new BLS.KeyPair();
}
String output = String.format(
"Secret Key: %s\nPublic Key: %s",
Base64.getEncoder().encodeToString(keyPair.getSecretKey().toBytes()),
Base64.getEncoder().encodeToString(keyPair.getPublicKey().toBytes())
);
if (outputFile != null) {
Files.writeString(outputFile, output);
System.out.println("Keys written to: " + outputFile);
} else {
System.out.println(output);
}
return 0;
}
}
@Command(name = "sign", description = "Sign a message")
static class SignCommand implements Callable<Integer> {
@Parameters(index = "0", description = "Message to sign")
private String message;
@Option(names = {"--key", "-k"}, description = "Secret key file", required = true)
private Path keyFile;
@Option(names = {"--output", "-o"}, description = "Output file")
private Path outputFile;
@Override
public Integer call() throws Exception {
// Read secret key
String keyContent = Files.readString(keyFile);
String skBase64 = extractSecretKey(keyContent);
byte[] skBytes = Base64.getDecoder().decode(skBase64);
BLSSecretKey sk = BLSSecretKey.fromBytes(skBytes);
// Sign message
byte[] signature = BLS.sign(sk, message.getBytes());
String sigBase64 = Base64.getEncoder().encodeToString(signature);
if (outputFile != null) {
Files.writeString(outputFile, sigBase64);
System.out.println("Signature written to: " + outputFile);
} else {
System.out.println("Signature: " + sigBase64);
}
return 0;
}
private String extractSecretKey(String content) {
String[] lines = content.split("\n");
for (String line : lines) {
if (line.startsWith("Secret Key:")) {
return line.substring("Secret Key:".length()).trim();
}
}
throw new IllegalArgumentException("No secret key found in file");
}
}
@Command(name = "verify", description = "Verify a signature")
static class VerifyCommand implements Callable<Integer> {
@Parameters(index = "0", description = "Message")
private String message;
@Option(names = {"--public-key", "-p"}, description = "Public key file", required = true)
private Path publicKeyFile;
@Option(names = {"--signature", "-s"}, description = "Signature file", required = true)
private Path signatureFile;
@Override
public Integer call() throws Exception {
// Read public key
String pkContent = Files.readString(publicKeyFile);
String pkBase64 = extractPublicKey(pkContent);
BLSPublicKey pk = BLSPublicKey.fromBytes(Base64.getDecoder().decode(pkBase64));
// Read signature
String sigBase64 = Files.readString(signatureFile).trim();
byte[] signature = Base64.getDecoder().decode(sigBase64);
// Verify
boolean valid = BLS.verify(pk, message.getBytes(), signature);
if (valid) {
System.out.println("✓ Signature is valid");
return 0;
} else {
System.out.println("✗ Signature is invalid");
return 1;
}
}
private String extractPublicKey(String content) {
String[] lines = content.split("\n");
for (String line : lines) {
if (line.startsWith("Public Key:")) {
return line.substring("Public Key:".length()).trim();
}
}
throw new IllegalArgumentException("No public key found in file");
}
}
@Command(name = "aggregate", description = "Aggregate signatures")
static class AggregateCommand implements Callable<Integer> {
@Parameters(index = "0..*", description = "Signature files to aggregate")
private List<Path> signatureFiles;
@Option(names = {"--output", "-o"}, description = "Output file")
private Path outputFile;
@Override
public Integer call() throws Exception {
List<BLSSignature> signatures = new ArrayList<>();
for (Path sigFile : signatureFiles) {
String sigBase64 = Files.readString(sigFile).trim();
byte[] sigBytes = Base64.getDecoder().decode(sigBase64);
signatures.add(BLSSignature.fromBytes(sigBytes));
}
BLSSignature aggregated = BLSSignature.aggregate(signatures);
String aggBase64 = Base64.getEncoder().encodeToString(aggregated.toBytes());
if (outputFile != null) {
Files.writeString(outputFile, aggBase64);
System.out.println("Aggregated signature written to: " + outputFile);
} else {
System.out.println("Aggregated Signature: " + aggBase64);
}
return 0;
}
}
@Command(name = "threshold", description = "Threshold signature operations")
static class ThresholdCommand {
@Command(name = "setup", description = "Setup threshold signature scheme")
Integer setup(
@Option(names = {"--threshold", "-t"}, description = "Threshold value", required = true)
int threshold,
@Option(names = {"--participants", "-n"}, description = "Number of participants", required = true)
int participants,
@Option(names = {"--output", "-o"}, description = "Output directory")
Path outputDir
) throws Exception {
ThresholdBLS thresholdBLS = new ThresholdBLS(threshold, participants);
ThresholdBLS.SharedSecret shared = thresholdBLS.generateSharedSecret();
if (outputDir != null) {
Files.createDirectories(outputDir);
// Write master public key
BLSPublicKey masterPK = thresholdBLS.getMasterPublicKey();
Files.writeString(outputDir.resolve("master.pub"),
Base64.getEncoder().encodeToString(masterPK.toBytes()));
// Write participant shares
List<ThresholdBLS.ShamirShare> shares = shared.getShares();
for (int i = 0; i < shares.size(); i++) {
ThresholdBLS.ShamirShare share = shares.get(i);
String shareContent = String.format("Index: %d\nShare: %s",
share.getIndex(),
Base64.getEncoder().encodeToString(share.getValue().toByteArray()));
Files.writeString(outputDir.resolve("share-" + (i + 1) + ".key"), shareContent);
}
System.out.println("Threshold keys written to: " + outputDir);
}
return 0;
}
}
public static void main(String[] args) {
int exitCode = new CommandLine(new BLSCLI())
.addSubcommand("keygen", new KeyGenCommand())
.addSubcommand("sign", new SignCommand())
.addSubcommand("verify", new VerifyCommand())
.addSubcommand("aggregate", new AggregateCommand())
.addSubcommand("threshold", new ThresholdCommand())
.execute(args);
System.exit(exitCode);
}
}

Testing

package com.bls.test;
import com.bls.core.*;
import com.bls.threshold.ThresholdBLS;
import com.bls.multisig.BLSMultiSignature;
import com.bls.pop.BLSPoP;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
class BLSTest {
@Test
void testKeyGeneration() {
BLS.KeyPair keyPair = new BLS.KeyPair();
assertNotNull(keyPair.getSecretKey());
assertNotNull(keyPair.getPublicKey());
assertNotEquals(0, keyPair.getSecretKey().toBytes().length);
assertNotEquals(0, keyPair.getPublicKey().toBytes().length);
}
@Test
void testSignAndVerify() {
BLS.KeyPair keyPair = new BLS.KeyPair();
byte[] message = "Hello, BLS!".getBytes(StandardCharsets.UTF_8);
byte[] signature = BLS.sign(keyPair.getSecretKey(), message);
boolean valid = BLS.verify(keyPair.getPublicKey(), message, signature);
assertTrue(valid);
}
@Test
void testSignAndVerifyWithDifferentMessage() {
BLS.KeyPair keyPair = new BLS.KeyPair();
byte[] message1 = "Message 1".getBytes();
byte[] message2 = "Message 2".getBytes();
byte[] signature = BLS.sign(keyPair.getSecretKey(), message1);
boolean valid = BLS.verify(keyPair.getPublicKey(), message2, signature);
assertFalse(valid);
}
@Test
void testSignatureAggregation() {
List<BLS.KeyPair> keyPairs = new ArrayList<>();
List<byte[]> signatures = new ArrayList<>();
List<byte[]> messages = new ArrayList<>();
for (int i = 0; i < 3; i++) {
BLS.KeyPair keyPair = new BLS.KeyPair();
keyPairs.add(keyPair);
byte[] message = ("Message " + i).getBytes();
messages.add(message);
byte[] signature = BLS.sign(keyPair.getSecretKey(), message);
signatures.add(signature);
}
// Aggregate signatures
List<BLSSignature> sigList = new ArrayList<>();
for (byte[] sig : signatures) {
sigList.add(BLSSignature.fromBytes(sig));
}
BLSSignature aggregated = BLSSignature.aggregate(sigList);
// Aggregate public keys
List<BLSPublicKey> pkList = new ArrayList<>();
for (BLS.KeyPair kp : keyPairs) {
pkList.add(kp.getPublicKey());
}
// Verify aggregate
boolean valid = BLSSignature.verifyAggregate(
sigList, pkList, messages
);
assertTrue(valid);
}
@Test
void testAggregateSameMessage() {
List<BLS.KeyPair> keyPairs = new ArrayList<>();
List<BLSSignature> signatures = new ArrayList<>();
byte[] message = "Common message".getBytes();
for (int i = 0; i < 5; i++) {
BLS.KeyPair keyPair = new BLS.KeyPair();
keyPairs.add(keyPair);
byte[] sig = BLS.sign(keyPair.getSecretKey(), message);
signatures.add(BLSSignature.fromBytes(sig));
}
BLSSignature aggregated = BLSSignature.aggregate(signatures);
List<BLSPublicKey> publicKeys = keyPairs.stream()
.map(BLS.KeyPair::getPublicKey)
.toList();
boolean valid = BLSSignature.verifyAggregateSameMessage(
aggregated, publicKeys, message
);
assertTrue(valid);
}
@Test
void testThresholdSignatures() throws Exception {
int threshold = 3;
int participants = 5;
ThresholdBLS thresholdBLS = new ThresholdBLS(threshold, participants);
ThresholdBLS.SharedSecret shared = thresholdBLS.generateSharedSecret();
thresholdBLS.distributeShares(shared.getShares());
byte[] message = "Threshold test".getBytes();
// Collect signatures from enough participants
Map<Integer, byte[]> signatures = new HashMap<>();
for (int i = 1; i <= threshold; i++) {
byte[] sig = thresholdBLS.signParticipant(i, message);
signatures.put(i, sig);
}
// Aggregate threshold signature
byte[] aggregated = thresholdBLS.aggregateThresholdSignatures(signatures, message);
// Verify
boolean valid = thresholdBLS.verifyThresholdSignature(aggregated, message);
assertTrue(valid);
}
@Test
void testProofOfPossession() {
BLSPoP.PoPKeyPair keyPair = new BLSPoP.PoPKeyPair();
boolean valid = BLSPoP.verifyPoP(keyPair.getPublicKey(), keyPair.getProofOfPossession());
assertTrue(valid);
// Test with wrong proof
BLSPoP.PoPKeyPair wrongKeyPair = new BLSPoP.PoPKeyPair();
valid = BLSPoP.verifyPoP(keyPair.getPublicKey(), wrongKeyPair.getProofOfPossession());
assertFalse(valid);
}
@Test
void testPoPRegistry() {
BLSPoP.PoPRegistry registry = new BLSPoP.PoPRegistry();
registry.register("alice");
registry.register("bob");
assertTrue(registry.verifyRegistration("alice"));
assertTrue(registry.verifyRegistration("bob"));
List<BLSPublicKey> validKeys = registry.getAllValidPublicKeys();
assertEquals(2, validKeys.size());
}
@Test
void testDeterministicKeyGeneration() {
byte[] seed = "test-seed".getBytes();
BLS.KeyPair keyPair1 = BLS.generateDeterministicKeyPair(seed);
BLS.KeyPair keyPair2 = BLS.generateDeterministicKeyPair(seed);
assertArrayEquals(
keyPair1.getSecretKey().toBytes(),
keyPair2.getSecretKey().toBytes()
);
assertArrayEquals(
keyPair1.getPublicKey().toBytes(),
keyPair2.getPublicKey().toBytes()
);
}
@Test
void testMultiSignature() {
BLSMultiSignature multiSig = new BLSMultiSignature();
// Add signers
multiSig.addSigner("alice");
multiSig.addSigner("bob");
multiSig.addSigner("charlie");
byte[] message = "Multi-signature test".getBytes();
List<String> signers = Arrays.asList("alice", "bob");
// Request signatures
multiSig.requestSignatures("test-1", message, signers);
// Verify individual signatures
assertTrue(multiSig.verifyIndividualSignatures(message));
// Aggregate
BLSMultiSignature.MultiSignatureResult result = multiSig.aggregateSignatures(
Base64.getEncoder().encodeToString(message)
);
// Verify aggregated signature
assertTrue(result.verify(message));
assertEquals(2, result.getParticipantCount());
}
@Test
void testSerialization() {
BLS.KeyPair keyPair = new BLS.KeyPair();
byte[] serialized = BLSSerializer.serializeKeyPair(keyPair);
BLS.KeyPair deserialized = BLSSerializer.deserializeKeyPair(serialized);
assertArrayEquals(
keyPair.getSecretKey().toBytes(),
deserialized.getSecretKey().toBytes()
);
assertArrayEquals(
keyPair.getPublicKey().toBytes(),
deserialized.getPublicKey().toBytes()
);
}
}

Native Implementation (C++ for JNI)

// bls_jni.cpp
#include <jni.h>
#include <bls.hpp>
#include <vector>
using namespace bls;
extern "C" {
JNIEXPORT jbyteArray JNICALL
Java_com_bls_nativebridge_BLSJNI_hashToG1(JNIEnv* env, jclass clazz,
jbyteArray message, jbyteArray dst) {
jbyte* msgBytes = env->GetByteArrayElements(message, nullptr);
jsize msgLen = env->GetArrayLength(message);
jbyte* dstBytes = env->GetByteArrayElements(dst, nullptr);
jsize dstLen = env->GetArrayLength(dst);
std::vector<uint8_t> msg(reinterpret_cast<uint8_t*>(msgBytes),
reinterpret_cast<uint8_t*>(msgBytes) + msgLen);
std::vector<uint8_t> dstVec(reinterpret_cast<uint8_t*>(dstBytes),
reinterpret_cast<uint8_t*>(dstBytes) + dstLen);
G1Element hash = G1Element::FromMessage(msg, dstVec);
std::vector<uint8_t> serialized = hash.Serialize();
jbyteArray result = env->NewByteArray(serialized.size());
env->SetByteArrayRegion(result, 0, serialized.size(),
reinterpret_cast<jbyte*>(serialized.data()));
env->ReleaseByteArrayElements(message, msgBytes, JNI_ABORT);
env->ReleaseByteArrayElements(dst, dstBytes, JNI_ABORT);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_com_bls_nativebridge_BLSJNI_g1ScalarMult(JNIEnv* env, jclass clazz,
jbyteArray point, jbyteArray scalar) {
jbyte* pointBytes = env->GetByteArrayElements(point, nullptr);
jbyte* scalarBytes = env->GetByteArrayElements(scalar, nullptr);
std::vector<uint8_t> pointVec(reinterpret_cast<uint8_t*>(pointBytes),
reinterpret_cast<uint8_t*>(pointBytes) + 
env->GetArrayLength(point));
std::vector<uint8_t> scalarVec(reinterpret_cast<uint8_t*>(scalarBytes),
reinterpret_cast<uint8_t*>(scalarBytes) + 
env->GetArrayLength(scalar));
G1Element g1 = G1Element::FromBytes(pointVec);
bn_t sk;
bn_read_bin(sk, scalarVec.data(), scalarVec.size());
G1Element resultG1 = g1 * sk;
std::vector<uint8_t> serialized = resultG1.Serialize();
jbyteArray result = env->NewByteArray(serialized.size());
env->SetByteArrayRegion(result, 0, serialized.size(),
reinterpret_cast<jbyte*>(serialized.data()));
env->ReleaseByteArrayElements(point, pointBytes, JNI_ABORT);
env->ReleaseByteArrayElements(scalar, scalarBytes, JNI_ABORT);
return result;
}
JNIEXPORT jboolean JNICALL
Java_com_bls_nativebridge_BLSJNI_verifyPairing(JNIEnv* env, jclass clazz,
jbyteArray sig, jbyteArray pk,
jbyteArray hash) {
jbyte* sigBytes = env->GetByteArrayElements(sig, nullptr);
jbyte* pkBytes = env->GetByteArrayElements(pk, nullptr);
jbyte* hashBytes = env->GetByteArrayElements(hash, nullptr);
G1Element signature = G1Element::FromBytes(
std::vector<uint8_t>(reinterpret_cast<uint8_t*>(sigBytes),
reinterpret_cast<uint8_t*>(sigBytes) + 
env->GetArrayLength(sig)));
G2Element publicKey = G2Element::FromBytes(
std::vector<uint8_t>(reinterpret_cast<uint8_t*>(pkBytes),
reinterpret_cast<uint8_t*>(pkBytes) + 
env->GetArrayLength(pk)));
G1Element hashElement = G1Element::FromBytes(
std::vector<uint8_t>(reinterpret_cast<uint8_t*>(hashBytes),
reinterpret_cast<uint8_t*>(hashBytes) + 
env->GetArrayLength(hash)));
bool result = (signature * publicKey == hashElement * G1Element::Generator());
env->ReleaseByteArrayElements(sig, sigBytes, JNI_ABORT);
env->ReleaseByteArrayElements(pk, pkBytes, JNI_ABORT);
env->ReleaseByteArrayElements(hash, hashBytes, JNI_ABORT);
return result;
}
}

Build Configuration

<!-- pom.xml with native compilation -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>compile-native</id>
<phase>compile</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>g++</executable>
<arguments>
<argument>-shared</argument>
<argument>-fPIC</argument>
<argument>-o</argument>
<argument>${project.build.directory}/libbls_jni.so</argument>
<argument>src/main/cpp/bls_jni.cpp</argument>
<argument>-I${java.home}/include</argument>
<argument>-I${java.home}/include/linux</argument>
<argument>-lbls384</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Conclusion

This comprehensive BLS signature implementation in Java provides:

  1. Core BLS Operations - Key generation, signing, verification
  2. Signature Aggregation - Combine multiple signatures
  3. Threshold Signatures - t-of-n signing schemes
  4. Multi-Signature - Multiple signers on same/different messages
  5. Proof-of-Possession - Key ownership verification
  6. Serialization - Efficient binary serialization
  7. JNI Bridge - High-performance native implementation
  8. CLI Tool - Command-line interface for common operations

Key features:

  • BLS12-381 curve (standard for BLS signatures)
  • Fast pairing operations
  • Compact signatures (48-96 bytes)
  • Aggregation for efficiency
  • Threshold schemes for distributed trust
  • Production-ready with extensive testing

This implementation is suitable for:

  • Blockchain consensus (Ethereum 2.0, Chia, Dfinity)
  • Distributed systems
  • Certificate transparency
  • Multi-party computation
  • Secure multi-signature wallets

The library provides both pure Java and native implementations, allowing you to choose between portability and performance based on your requirements.

Advanced Java Programming Concepts and Projects (Related to Java Programming)


Number Guessing Game in Java:
This project teaches how to build a simple number guessing game using Java. It combines random number generation, loops, and conditional statements to create an interactive program where users guess a number until they find the correct answer.
Read more: https://macronepal.com/blog/number-guessing-game-in-java-a-complete-guide/


HashMap Basics in Java:
HashMap is a collection class used to store data in key-value pairs. It allows fast retrieval of values using keys and is widely used when working with structured data that requires quick searching and updating.
Read more: https://macronepal.com/blog/hashmap-basics-in-java-a-complete-guide/


Date and Time in Java:
This topic explains how to work with dates and times in Java using built-in classes. It helps developers manage time-related data such as current date, formatting time, and calculating time differences.
Read more: https://macronepal.com/blog/date-and-time-in-java-a-complete-guide/


StringBuilder in Java:
StringBuilder is used to create and modify strings efficiently. Unlike regular strings, it allows changes without creating new objects, making programs faster when handling large or frequently changing text.
Read more: https://macronepal.com/blog/stringbuilder-in-java-a-complete-guide/


Packages in Java:
Packages help organize Java classes into groups, making programs easier to manage and maintain. They also help prevent naming conflicts and improve code structure in large applications.
Read more: https://macronepal.com/blog/packages-in-java-a-complete-guide/


Interfaces in Java:
Interfaces define a set of methods that classes must implement. They help achieve abstraction and support multiple inheritance in Java, making programs more flexible and organized.
Read more: https://macronepal.com/blog/interfaces-in-java-a-complete-guide/


Abstract Classes in Java:
Abstract classes are classes that cannot be instantiated directly and may contain both abstract and non-abstract methods. They are used as base classes to define common features for other classes.
Read more: https://macronepal.com/blog/abstract-classes-in-java-a-complete-guide/


Method Overriding in Java:
Method overriding occurs when a subclass provides its own version of a method already defined in its parent class. It supports runtime polymorphism and allows customized behavior in child classes.
Read more: https://macronepal.com/blog/method-overriding-in-java-a-complete-guide/


The This Keyword in Java:
The this keyword refers to the current object in a class. It is used to access instance variables, call constructors, and differentiate between class variables and parameters.
Read more: https://macronepal.com/blog/the-this-keyword-in-java-a-complete-guide/


Encapsulation in Java:
Encapsulation is an object-oriented concept that involves bundling data and methods into a single unit and restricting direct access to some components. It improves data security and program organization.
Read more: https://macronepal.com/blog/encapsulation-in-java-a-complete-guide/

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper