Tezex.Crypto.BLS (tezex v3.2.0)

View Source

Pure Elixir BLS12-381 cryptographic operations for Tezos tz4 addresses.

This module provides BLS functionality:

  • Private key operations (from 32-byte seeds or HKDF key generation)
  • Public key derivation (48-byte compressed G1 format)
  • Message signing and verification with multiple ciphersuites
  • Serialization/deserialization compatible with Tezos formats
  • Support for message augmentation, basic, and proof-of-possession schemes

Based on the BLS12-381 curve with IETF standard ciphersuites:

  • G2Basic: BLSSIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL
  • G2MessageAugmentation: BLSSIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG (default)
  • G2ProofOfPossession: BLSSIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP
  • MinPk variant (48-byte public keys, 96-byte signatures)
  • Hash-to-curve with SSWU mapping

Partly ported from pytezos@439bafada8f063f9643c789f6154d4646674d66a

pytezos / MIT License / (c) 2020 Baking Bad / (c) 2018 Arthur Breitman and py_ecc@04151f01f59f902ab932a51e0ca0ebce3883fc51 py_ecc / MIT License / (c) (c) 2015 Vitalik Buterin

Summary

Functions

Returns the supported BLS ciphersuites and their domain separation tags.

Deserializes a BLS private key from bytes.

Converts a hex string to binary, handling both uppercase and lowercase.

Creates a BLS private key from a secret exponent (integer or bytes).

Creates a new BLS private key from a 32-byte seed.

Derives the BLS public key from the private key. Returns a 48-byte compressed G1 point.

Generates a BLS private key using HKDF key generation (IETF standard).

Generates a proof-of-possession for a BLS public key.

Verifies a proof-of-possession for a BLS public key.

Serializes a BLS private key to bytes.

Signs a message with the BLS private key using the specified ciphersuite. Returns a 96-byte signature.

Returns the expected sizes for BLS keys and signatures.

Converts binary to hex string (lowercase).

Validates a BLS public key to ensure it's a valid G1 point. Infinity points are rejected for public keys .

Validates a BLS signature to ensure it's a valid G2 point. Infinity points are allowed for signatures (unlike public keys).

Verifies a BLS signature against a message and public key using the specified ciphersuite.

Types

ciphersuite()

@type ciphersuite() :: :basic | :message_augmentation | :proof_of_possession

key_info()

@type key_info() :: binary()

public_key()

@type public_key() :: binary()

signature()

@type signature() :: binary()

t()

@type t() :: %Tezex.Crypto.BLS{secret_key: Tezex.Crypto.BLS.Fr.t()}

Functions

ciphersuites()

@spec ciphersuites() :: %{
  basic: String.t(),
  message_augmentation: String.t(),
  proof_of_possession: String.t(),
  pop_tag: String.t()
}

Returns the supported BLS ciphersuites and their domain separation tags.

deserialize_secret_key(secret_bytes)

@spec deserialize_secret_key(binary()) :: {:ok, t()} | {:error, :invalid_key}

Deserializes a BLS private key from bytes.

from_hex(hex_string)

@spec from_hex(String.t()) :: {:ok, binary()} | {:error, :invalid_hex}

Converts a hex string to binary, handling both uppercase and lowercase.

from_secret_exponent(secret)

@spec from_secret_exponent(binary() | integer()) ::
  {:ok, t()} | {:error, :invalid_secret}

Creates a BLS private key from a secret exponent (integer or bytes).

Examples

iex> secret_bytes = :crypto.strong_rand_bytes(32)
iex> {:ok, bls} = Tezex.Crypto.BLS.from_secret_exponent(secret_bytes)
iex> byte_size(Tezex.Crypto.BLS.serialize_secret_key(bls))
32

from_seed(seed)

@spec from_seed(binary()) :: {:ok, t()} | {:error, :invalid_seed}

Creates a new BLS private key from a 32-byte seed.

This matches octez-client behavior by using the seed directly as the scalar, without any key derivation function.

Examples

iex> seed = :crypto.strong_rand_bytes(32)
iex> {:ok, bls} = Tezex.Crypto.BLS.from_seed(seed)
iex> byte_size(Tezex.Crypto.BLS.Fr.to_bytes(bls.secret_key))
32

get_public_key(bls)

@spec get_public_key(t()) :: public_key()

Derives the BLS public key from the private key. Returns a 48-byte compressed G1 point.

Examples

iex> seed = :crypto.strong_rand_bytes(32)
iex> {:ok, bls} = Tezex.Crypto.BLS.from_seed(seed)
iex> pubkey = Tezex.Crypto.BLS.get_public_key(bls)
iex> byte_size(pubkey)
48

key_gen(ikm, key_info \\ <<>>)

@spec key_gen(binary(), key_info()) :: {:ok, t()} | {:error, :invalid_ikm}

Generates a BLS private key using HKDF key generation (IETF standard).

This follows the KeyGen algorithm from the IETF BLS specification.

Parameters

  • ikm: Input key material (typically 32+ bytes of entropy)
  • key_info: Optional key info for domain separation (default: empty)

Examples

iex> ikm = :crypto.strong_rand_bytes(32)
iex> {:ok, bls} = Tezex.Crypto.BLS.key_gen(ikm)
iex> is_struct(bls, Tezex.Crypto.BLS)
true

pop_prove(bls)

@spec pop_prove(t()) :: signature()

Generates a proof-of-possession for a BLS public key.

This proves that the holder knows the corresponding private key, following the IETF BLS proof-of-possession specification.

pop_verify(public_key, proof_of_possession)

@spec pop_verify(public_key(), signature()) :: boolean()

Verifies a proof-of-possession for a BLS public key.

serialize_secret_key(bls)

@spec serialize_secret_key(t()) :: binary()

Serializes a BLS private key to bytes.

sign(bls_key, message, ciphersuite \\ :message_augmentation)

@spec sign(t(), binary(), ciphersuite()) :: signature()

Signs a message with the BLS private key using the specified ciphersuite. Returns a 96-byte signature.

Ciphersuites

  • :message_augmentation (default): Message augmented with public key (most secure)
  • :basic: Basic BLS signature (requires message uniqueness for aggregation)
  • :proof_of_possession: Proof-of-possession scheme

Examples

iex> seed = :crypto.strong_rand_bytes(32)
iex> {:ok, bls} = Tezex.Crypto.BLS.from_seed(seed)
iex> message = "test message"
iex> signature = Tezex.Crypto.BLS.sign(bls, message)
iex> byte_size(signature)
96

sizes()

@spec sizes() :: %{
  secret_key: pos_integer(),
  public_key: pos_integer(),
  signature: pos_integer()
}

Returns the expected sizes for BLS keys and signatures.

to_hex(binary)

@spec to_hex(binary()) :: String.t()

Converts binary to hex string (lowercase).

validate_public_key(public_key)

@spec validate_public_key(public_key()) :: boolean()

Validates a BLS public key to ensure it's a valid G1 point. Infinity points are rejected for public keys .

validate_signature(signature)

@spec validate_signature(signature()) :: boolean()

Validates a BLS signature to ensure it's a valid G2 point. Infinity points are allowed for signatures (unlike public keys).

verify(signature, message, public_key, ciphersuite \\ :message_augmentation)

@spec verify(signature(), binary(), public_key(), ciphersuite()) :: boolean()

Verifies a BLS signature against a message and public key using the specified ciphersuite.

Ciphersuites

  • :message_augmentation (default): Message augmented with public key
  • :basic: Basic BLS signature
  • :proof_of_possession: Proof-of-possession scheme

Examples

iex> seed = :crypto.strong_rand_bytes(32)
iex> {:ok, bls} = Tezex.Crypto.BLS.from_seed(seed)
iex> message = "test message"
iex> pubkey = Tezex.Crypto.BLS.get_public_key(bls)
iex> signature = Tezex.Crypto.BLS.sign(bls, message)
iex> Tezex.Crypto.BLS.verify(signature, message, pubkey)
true