mta - Multiplicative-to-Additive Conversion¶
Overview¶
The mta module implements Multiplicative-to-Additive (MtA) share conversion,
a fundamental building block for threshold ECDSA protocols. MtA enables two parties
holding multiplicative shares (a, b) to convert them into additive shares (α, β)
such that a·b = α + β (mod q), without revealing their individual shares.
This implementation is based on the DKLS23 paper (“Threshold ECDSA from ECDSA Assumptions: The Multiparty Case”) and uses real oblivious transfer (OT) for security rather than simulation.
Key Features¶
Secure Share Conversion: Converts multiplicative to additive shares without revealing inputs
OT-Based Security: Uses real SimpleOT for cryptographic security guarantees
MtA with Check (MtAwc): Includes zero-knowledge proofs for malicious security
Curve Agnostic: Works with any DDH-hard elliptic curve group
Bit-Level OT: Uses correlated OT with one OT per bit of the secret
Security Properties¶
Sender Privacy: Receiver learns nothing about sender’s share beyond the additive output
Receiver Privacy: Sender learns nothing about receiver’s share
Correctness: The additive shares always sum to the original product
Malicious Security (MtAwc): Zero-knowledge proofs detect cheating parties
Use Cases¶
DKLS23 Presigning: Core component for generating presignatures
Threshold ECDSA: Enables secure multiplication of secret shares
Two-Party Computation: General-purpose secure multiplication protocol
Example Usage¶
Basic MtA Conversion:
from charm.toolbox.ecgroup import ECGroup, ZR
from charm.toolbox.eccurve import secp256k1
from charm.toolbox.mta import MtA
group = ECGroup(secp256k1)
# Create separate MtA instances for Alice and Bob
alice_mta = MtA(group)
bob_mta = MtA(group)
# Alice has share a, Bob has share b
a = group.random(ZR)
b = group.random(ZR)
# Run the 4-round MtA protocol
sender_msg = alice_mta.sender_round1(a)
receiver_msg, _ = bob_mta.receiver_round1(b, sender_msg)
alpha, ot_data = alice_mta.sender_round2(receiver_msg)
beta = bob_mta.receiver_round2(ot_data)
# Verify: a*b = alpha + beta (mod q)
assert a * b == alpha + beta
MtA with Correctness Check (MtAwc):
from charm.toolbox.mta import MtAwc
mta_wc = MtAwc(group)
a = group.random(ZR)
b = group.random(ZR)
# Commitment phase
sender_commit = mta_wc.sender_commit(a)
receiver_commit = mta_wc.receiver_commit(b)
# MtA with ZK proofs
sender_msg = mta_wc.sender_round1(a, receiver_commit)
receiver_msg, _ = mta_wc.receiver_round1(b, sender_commit, sender_msg)
alpha, proof = mta_wc.sender_round2(receiver_msg)
beta, valid = mta_wc.receiver_verify(proof)
assert valid # Proof verified
assert a * b == alpha + beta
Protocol Details¶
The MtA protocol works as follows:
Sender Setup: Sender decomposes share a into bits and creates OT sender instances
Receiver Choose: Receiver uses bits of b to select OT messages (learns only selected values)
Sender Transfer: Sender encrypts correlated messages via OT
Receiver Output: Receiver decrypts selected messages and computes additive share
For each bit position i, the correlation is: m₁ = m₀ + a·2ⁱ, ensuring the sum of selected values equals a·b.
API Reference¶
Multiplicative-to-Additive (MtA) Share Conversion for DKLS23
type: share conversion
setting: Elliptic Curve DDH-hard group
assumption: DDH + OT security
MtA converts multiplicative shares (a, b) where two parties hold a and b to additive shares (alpha, beta) such that a*b = alpha + beta (mod q). Neither party learns the other’s share.
- Authors:
Elton de Souza
- Date:
01/2026
Bases:
objectCorrelated Oblivious Transfer for MtA.
Generates correlated random values for OT-based MtA. For each bit of the sender’s input, generates correlation pairs.
- class mta.MtA(groupObj: Any)[source]¶
Bases:
objectMultiplicative-to-Additive share conversion using OT.
Converts multiplicative shares (a, b) where parties hold a and b to additive shares (alpha, beta) where a*b = alpha + beta (mod q).
Curve Agnostic¶
This implementation supports any elliptic curve group that is DDH-hard. The curve is specified via the groupObj parameter.
The protocol works as follows: 1. Sender (holding a) decomposes a into bits 2. For each bit position i, run correlated OT with correlation 2^i * b 3. Receiver (holding b) chooses based on sender’s bits 4. Parties compute their additive shares from OT outputs
>>> from charm.toolbox.eccurve import secp256k1 >>> group = ECGroup(secp256k1) >>> # Create separate instances for Alice (sender) and Bob (receiver) >>> alice_mta = MtA(group) >>> bob_mta = MtA(group) >>> # Alice has share a, Bob has share b >>> a = group.random(ZR) >>> b = group.random(ZR) >>> # Convert to additive shares using the protocol with real OT >>> sender_msg = alice_mta.sender_round1(a) >>> receiver_msg, _ = bob_mta.receiver_round1(b, sender_msg) >>> alpha, ot_data = alice_mta.sender_round2(receiver_msg) >>> beta = bob_mta.receiver_round2(ot_data) >>> # Verify: a*b = alpha + beta (mod q) >>> product = a * b >>> additive_sum = alpha + beta >>> product == additive_sum True
- receiver_complete(sender_bits: List[int]) Any[source]¶
Receiver returns their additive share beta (already computed).
Parameters¶
- sender_bitslist of int
Sender’s bit decomposition (unused in correct protocol)
Returns¶
- ZR element
Receiver’s additive share beta
- receiver_round1(b: Any, sender_msg: Dict[str, Any]) Tuple[Dict[str, Any], None][source]¶
Receiver (holding b) selects OT messages based on bits of b.
Uses real SimpleOT: for each bit b_i, receiver only learns m_{b_i}. The receiver NEVER sees both m0 and m1.
Parameters¶
- bZR element
Receiver’s multiplicative share
- sender_msgdict
Message from sender_round1
Returns¶
- tuple (dict, None)
A tuple containing: - dict: Receiver parameters with ‘ot_responses’ list of OT receiver responses - None: Placeholder for beta (computed in receiver_round2)
- receiver_round2(sender_round2_msg: Dict[str, Any]) Any[source]¶
Receiver retrieves selected OT messages and computes beta.
Parameters¶
- sender_round2_msgdict
Message from sender_round2 containing OT ciphertexts
Returns¶
- ZR element
Receiver’s additive share beta such that a*b = alpha + beta (mod q)
- sender_round1(a: Any) Dict[str, Any][source]¶
Sender (holding a) generates first message.
Sender samples random alpha and prepares OT messages such that receiver can learn beta = a*b - alpha. Uses real SimpleOT for security.
Parameters¶
- aZR element
Sender’s multiplicative share
Returns¶
- dict
OT setup parameters containing: - ‘ot_params’: list of OT sender parameters (one per bit position) - ‘adjustment’: integer for receiver to compute beta
- sender_round2(receiver_msg: Dict[str, Any]) Tuple[Any, Dict[str, Any]][source]¶
Sender processes receiver’s OT responses and returns alpha.
Parameters¶
- receiver_msgdict
Message from receiver_round1 containing OT responses
Returns¶
- tuple (ZR element, dict)
A tuple containing: - ZR element: Sender’s additive share alpha - dict: OT data with ‘ot_ciphertexts’ list for receiver to retrieve
- class mta.MtAwc(groupObj: Any)[source]¶
Bases:
objectMtA with check - includes ZK proof that conversion is correct.
Used for malicious security. Adds commitment and proof phases to verify that parties performed MtA correctly.
The protocol adds: 1. Commitment phase: parties commit to their shares 2. Proof phase: parties prove correctness of OT selections 3. Verification: parties verify each other’s proofs
>>> from charm.toolbox.eccurve import secp256k1 >>> group = ECGroup(secp256k1) >>> mta_wc = MtAwc(group) >>> # Alice has share a, Bob has share b >>> a = group.random(ZR) >>> b = group.random(ZR) >>> # Run MtA with correctness check >>> sender_commit = mta_wc.sender_commit(a) >>> receiver_commit = mta_wc.receiver_commit(b) >>> # Exchange commitments and run MtA >>> sender_msg = mta_wc.sender_round1(a, receiver_commit) >>> receiver_msg, _ = mta_wc.receiver_round1(b, sender_commit, sender_msg) >>> alpha, sender_proof = mta_wc.sender_round2(receiver_msg) >>> beta, valid = mta_wc.receiver_verify(sender_proof) >>> valid True >>> # Verify: a*b = alpha + beta (mod q) >>> product = a * b >>> additive_sum = alpha + beta >>> product == additive_sum True
- receiver_commit(b: Any) Dict[str, Any][source]¶
Receiver commits to share b.
Parameters¶
- bZR element
Receiver’s multiplicative share
Returns¶
- dict
Commitment to send to sender
- receiver_round1(b: Any, sender_commit: Dict[str, Any], sender_msg: Dict[str, Any]) Tuple[Dict[str, Any], None][source]¶
Receiver processes sender message with commitments.
Parameters¶
- bZR element
Receiver’s multiplicative share
- sender_commitdict
Sender’s commitment from sender_commit (includes bit_proof)
- sender_msgdict
Message from sender_round1
Returns¶
- tuple
(receiver_message, beta_placeholder)
- receiver_verify(proof: Dict[str, Any]) Tuple[Any | None, bool][source]¶
Receiver verifies proof including ZK bit decomposition and returns beta.
Implements full ZK verification per DKLS23 Section 3: 1. Verifies challenge-response for commitment 2. Verifies bit decomposition OR-proofs (each bit is 0 or 1) 3. Verifies bits sum to the committed value
Parameters¶
- proofdict
Proof from sender_round2
Returns¶
- tuple
(beta, valid) where: - beta: receiver’s additive share - valid: boolean indicating if proof is valid
- sender_commit(a: Any) Dict[str, Any][source]¶
Sender commits to share a with ZK bit decomposition proof.
Parameters¶
- aZR element
Sender’s multiplicative share
Returns¶
- dict
Commitment and bit decomposition proof to send to receiver
- sender_round1(a: Any, receiver_commit: Dict[str, Any]) Dict[str, Any][source]¶
Sender generates first message with receiver’s commitment.
Parameters¶
- aZR element
Sender’s multiplicative share
- receiver_commitdict
Receiver’s commitment from receiver_commit
Returns¶
- dict
Message to send to receiver
- sender_round2(receiver_msg: Dict[str, Any]) Tuple[Any, Dict[str, Any]][source]¶
Sender completes MtA and generates proof.
Parameters¶
- receiver_msgdict
Message from receiver_round1
Returns¶
- tuple
(alpha, proof) where: - alpha: sender’s additive share - proof: ZK proof of correctness (does NOT reveal sender_bits)
- mta.hash_to_field(group: Any, *args: Any) Any[source]¶
Hash multiple values to a field element with domain separation.
Uses group.hash() for proper domain separation and automatic serialization of different types.
Parameters¶
- groupECGroup
The elliptic curve group
- *argsvarious
Values to hash
Returns¶
- ZR element
Hash output as field element