Skip to content

shares

blind_message(message)

Blind a message in G2.

Computes H(m)' = r · H(m) where H(m) is the hash of the message and r is a non-zero scalar in Z_q.

Parameters:

Name Type Description Default
message bytes

Message to blind.

required

Returns:

Type Description
tuple[G2_Point, int]

Tuple of (blinded message, blinding factor).

Source code in src/payment/crypto/shares.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
def blind_message(message: bytes) -> tuple[G2_Point, int]:
    """Blind a message in G2.

    Computes H(m)' = r · H(m) where H(m) is the hash of the message and r is a
    non-zero scalar in Z_q.

    Args:
        message: Message to blind.

    Returns:
        Tuple of (blinded message, blinding factor).
    """

    r = secrets.randbelow(curve_order - 1) + 1
    message_point = hash_to_G2(message, G2Basic.DST, G2Basic.xmd_hash_function)
    blinded_point = multiply(message_point, r)

    return G2_Point.from_g2(blinded_point), r

combine_partial_signatures(partial_signatures)

Combine partial signatures using Lagrange interpolation in the exponent.

Computes σ = Σ_i λ_i · σ_i where σ_i are partial signatures and λ_i are Lagrange coefficients. Results in σ = H(m)^{SK}.

Parameters:

Name Type Description Default
partial_signatures list[PartialSignature]

List of partial signatures to combine.

required

Returns:

Type Description
G2_Point

Combined signature as G2_Point.

Source code in src/payment/crypto/shares.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def combine_partial_signatures(partial_signatures: list[PartialSignature]) -> G2_Point:
    """Combine partial signatures using Lagrange interpolation in the exponent.

    Computes σ = Σ_i λ_i · σ_i where σ_i are partial signatures and λ_i are
    Lagrange coefficients. Results in σ = H(m)^{SK}.

    Args:
        partial_signatures: List of partial signatures to combine.

    Returns:
        Combined signature as G2_Point.
    """

    ids = [p.id for p in partial_signatures]
    signature = Z2
    for p in partial_signatures:
        lam = lagrange_coefficient(p.id, ids)
        signature = add(signature, multiply(p.signature.to_g2(), lam))
    return G2_Point.from_g2(signature)

create_fresh_key_pair()

Create a new public/private key pair for a client.

Returns:

Type Description
tuple[G1_Point, int]

Tuple of (public key, private key).

Source code in src/payment/crypto/shares.py
242
243
244
245
246
247
248
249
250
251
def create_fresh_key_pair() -> tuple[G1_Point, int]:
    """Create a new public/private key pair for a client.

    Returns:
        Tuple of (public key, private key).
    """

    private_key = secrets.randbelow(curve_order - 1) + 1
    public_key = multiply(G1, private_key)
    return G1_Point.from_g1(public_key), private_key

evaluate_polynomial(coeffs, x)

Evaluate polynomial at point x using Horner's method.

Computes g(x) = a_0 + a_1·x + a_2·x² + ... mod q efficiently.

Parameters:

Name Type Description Default
coeffs list[int]

Polynomial coefficients [a_0, a_1, ..., a_n].

required
x int

Evaluation point.

required

Returns:

Type Description
int

g(x) mod q.

Source code in src/payment/crypto/shares.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def evaluate_polynomial(coeffs: list[int], x: int) -> int:
    """Evaluate polynomial at point x using Horner's method.

    Computes g(x) = a_0 + a_1·x + a_2·x² + ... mod q efficiently.

    Args:
        coeffs: Polynomial coefficients [a_0, a_1, ..., a_n].
        x: Evaluation point.

    Returns:
        g(x) mod q.
    """

    result = 0
    for a in reversed(coeffs):
        result = (result * x + a) % curve_order
    return result

generate_polynomial(degree)

Generate a random polynomial of specified degree over Z_q.

Creates g(x) = a_0 + a_1·x + ... + a_degree·x^degree where coefficients are randomly sampled from [0, q) with q being the BLS12-381 curve order.

Parameters:

Name Type Description Default
degree int

Polynomial degree (number of coefficients = degree + 1).

required

Returns:

Type Description
list[int]

List of coefficients [a_0, a_1, ..., a_degree].

Source code in src/payment/crypto/shares.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def generate_polynomial(degree: int) -> list[int]:
    """Generate a random polynomial of specified degree over Z_q.

    Creates g(x) = a_0 + a_1·x + ... + a_degree·x^degree where coefficients are
    randomly sampled from [0, q) with q being the BLS12-381 curve order.

    Args:
        degree: Polynomial degree (number of coefficients = degree + 1).

    Returns:
        List of coefficients [a_0, a_1, ..., a_degree].
    """

    return [secrets.randbelow(curve_order - 1) + 1 for _ in range(degree + 1)]

generate_shares(n, f)

Generate secret and n shares using Shamir's Secret Sharing.

Creates a random polynomial g(x) of degree f, then generates: - Secret key SK = g(0) - Public key PK = SK·G1 - n shares s_i = g(i) for i = 1, ..., n

Parameters:

Name Type Description Default
n int

Total number of shares.

required
f int

Polynomial degree (threshold = f+1).

required

Returns:

Type Description
tuple[int, list[KeyShare]]

Tuple of (secret_key, shares).

Source code in src/payment/crypto/shares.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def generate_shares(n: int, f: int) -> tuple[int, list[KeyShare]]:
    """Generate secret and n shares using Shamir's Secret Sharing.

    Creates a random polynomial g(x) of degree f, then generates:
    - Secret key SK = g(0)
    - Public key PK = SK·G1
    - n shares s_i = g(i) for i = 1, ..., n

    Args:
        n: Total number of shares.
        f: Polynomial degree (threshold = f+1).

    Returns:
        Tuple of (secret_key, shares).
    """

    coeffs = generate_polynomial(f)
    secret_key = coeffs[0]
    public_key = multiply(G1, secret_key)
    return secret_key, [
        KeyShare(
            id=i,
            share=evaluate_polynomial(coeffs, i),
            public_key=G1_Point.from_g1(public_key),
        )
        for i in range(1, n + 1)
    ]

generate_shares_to_files(n, f, shares_dir)

Generate shares and save them to files.

Parameters:

Name Type Description Default
n int

Total number of shares.

required
f int

Polynomial degree (threshold = f+1).

required
shares_dir str

Directory to save shares.

required
Source code in src/payment/crypto/shares.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def generate_shares_to_files(n: int, f: int, shares_dir: str) -> None:
    """Generate shares and save them to files.

    Args:
        n: Total number of shares.
        f: Polynomial degree (threshold = f+1).
        shares_dir: Directory to save shares.
    """

    _, shares = generate_shares(n, f)
    for i, share in enumerate(shares):
        with open(os.path.join(shares_dir, f"share_{i}.bin"), "wb") as file:
            file.write(share.model_dump_json().encode())
    with open(os.path.join(shares_dir, "system_public_key.bin"), "wb") as file:
        file.write(shares[0].public_key.model_dump_json().encode())

lagrange_coefficient(i, points)

Compute Lagrange coefficient λ_i for interpolation at x=0.

Computes λ_i = ∏_{j≠i} (-x_j)/(x_i - x_j) mod q, used in Lagrange interpolation to reconstruct g(0) = Σ_i y_i · λ_i from points {(x_i, y_i)}.

Parameters:

Name Type Description Default
i int

Index for which to compute coefficient.

required
points list[int]

List of all point indices.

required

Returns:

Type Description
int

Lagrange coefficient λ_i mod q.

Source code in src/payment/crypto/shares.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def lagrange_coefficient(i: int, points: list[int]) -> int:
    """Compute Lagrange coefficient λ_i for interpolation at x=0.

    Computes λ_i = ∏_{j≠i} (-x_j)/(x_i - x_j) mod q, used in Lagrange interpolation
    to reconstruct g(0) = Σ_i y_i · λ_i from points {(x_i, y_i)}.

    Args:
        i: Index for which to compute coefficient.
        points: List of all point indices.

    Returns:
        Lagrange coefficient λ_i mod q.
    """

    num = 1
    den = 1
    for j in points:
        if j == i:
            continue
        num = (num * (-j)) % curve_order
        den = (den * (i - j)) % curve_order
    return (num * pow(den, curve_order - 2, curve_order)) % curve_order

partial_sign_blinded_message(blinded_message, key_share)

Sign a blinded message using a key share.

Parameters:

Name Type Description Default
blinded_message G2_Point

Blinded message to sign.

required
key_share KeyShare

KeyShare with id and share value s_i.

required

Returns:

Type Description
PartialSignature

PartialSignature with id and signature point in G2.

Source code in src/payment/crypto/shares.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def partial_sign_blinded_message(
    blinded_message: G2_Point, key_share: KeyShare
) -> PartialSignature:
    """Sign a blinded message using a key share.

    Args:
        blinded_message: Blinded message to sign.
        key_share: KeyShare with id and share value s_i.

    Returns:
        PartialSignature with id and signature point in G2.
    """

    signature = multiply(blinded_message.to_g2(), key_share.share)
    return PartialSignature(id=key_share.id, signature=G2_Point.from_g2(signature))

reconstruct_secret(shares)

Reconstruct secret from shares using Lagrange interpolation.

Computes g(0) = Σ_i s_i · λ_i mod q from shares s_i = g(i). Requires at least f+1 shares for a degree-f polynomial.

Parameters:

Name Type Description Default
shares list[KeyShare]

List of KeyShare objects with id and share value.

required

Returns:

Type Description
int

Reconstructed secret g(0) mod q.

Source code in src/payment/crypto/shares.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def reconstruct_secret(shares: list[KeyShare]) -> int:
    """Reconstruct secret from shares using Lagrange interpolation.

    Computes g(0) = Σ_i s_i · λ_i mod q from shares s_i = g(i).
    Requires at least f+1 shares for a degree-f polynomial.

    Args:
        shares: List of KeyShare objects with id and share value.

    Returns:
        Reconstructed secret g(0) mod q.
    """

    return (
        sum(
            share.share
            * lagrange_coefficient(share.id, [share.id for share in shares])
            % curve_order
            for share in shares
        )
        % curve_order
    )

sign_message(message, private_key)

Sign a message using a private key.

Returns:

Type Description
G2_Point

Signature as G2_Point.

Source code in src/payment/crypto/shares.py
254
255
256
257
258
259
260
261
262
263
def sign_message(message: bytes, private_key: int) -> G2_Point:
    """Sign a message using a private key.

    Returns:
        Signature as G2_Point.
    """

    message_point = hash_to_G2(message, G2Basic.DST, G2Basic.xmd_hash_function)
    signature = multiply(message_point, private_key)
    return G2_Point.from_g2(signature)

unblind_signature(blinded_signature, blinding_factor)

Remove the blinding factor from a combined blind signature.

Computes σ = r⁻¹ · σ' where σ' is the combined (still-blinded) threshold signature. The result is a standard BLS signature σ = H(m)^{SK} that verifies under :func:verify_signature.

The algebraic identity is

σ' = SK · (r · H(m)) = r · (SK · H(m)) σ = r⁻¹ · σ' = SK · H(m)

Parameters:

Name Type Description Default
blinded_signature G2_Point

Combined blinded signature σ' ∈ G2.

required
blinding_factor int

The same scalar r used in :func:blind_message.

required

Returns:

Type Description
G2_Point

Unblinded BLS signature σ ∈ G2, valid under the system public key.

Source code in src/payment/crypto/shares.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
def unblind_signature(blinded_signature: G2_Point, blinding_factor: int) -> G2_Point:
    """Remove the blinding factor from a combined blind signature.

    Computes σ = r⁻¹ · σ' where σ' is the combined (still-blinded)
    threshold signature.  The result is a standard BLS signature
    σ = H(m)^{SK} that verifies under :func:`verify_signature`.

    The algebraic identity is:
        σ' = SK · (r · H(m)) = r · (SK · H(m))
        σ  = r⁻¹ · σ' = SK · H(m)

    Args:
        blinded_signature: Combined blinded signature σ' ∈ G2.
        blinding_factor: The same scalar r used in :func:`blind_message`.

    Returns:
        Unblinded BLS signature σ ∈ G2, valid under the system public key.
    """

    r_inv = pow(blinding_factor, curve_order - 2, curve_order)
    sig = multiply(blinded_signature.to_g2(), r_inv)
    return G2_Point.from_g2(sig)

verify_signature(message, signature, public_key)

Verify BLS signature using pairing check: e(σ, G1) = e(H(m), PK).

Parameters:

Name Type Description Default
message bytes

Signed message.

required
signature G2_Point

BLS signature σ ∈ G2.

required
public_key G1_Point

Public key PK ∈ G1.

required

Returns:

Type Description
bool

True if signature is valid, False otherwise.

Source code in src/payment/crypto/shares.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def verify_signature(message: bytes, signature: G2_Point, public_key: G1_Point) -> bool:
    """Verify BLS signature using pairing check: e(σ, G1) = e(H(m), PK).

    Args:
        message: Signed message.
        signature: BLS signature σ ∈ G2.
        public_key: Public key PK ∈ G1.

    Returns:
        True if signature is valid, False otherwise.
    """

    message_point = hash_to_G2(message, G2Basic.DST, G2Basic.xmd_hash_function)

    lhs = pairing(signature.to_g2(), G1)
    rhs = pairing(message_point, public_key.to_g1())

    return lhs == rhs