diff --git a/README.md b/README.md index 075687a..dfc16a2 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Explore the `examples` folder for more detailed usage scenarios. For local development, you can install the package in editable mode, which allows you to make changes and test them immediately: ```bash -python3 -m pip install . +python3 -m pip install -e . ``` After making changes, it's important to ensure all tests pass by running: diff --git a/examples/example_bls_threshold.py b/examples/example_bls_threshold.py new file mode 100644 index 0000000..49ef299 --- /dev/null +++ b/examples/example_bls_threshold.py @@ -0,0 +1,27 @@ +from pactus.crypto.bls.private_key import PrivateKey + + +# Example of BLS threshold signature aggregation using Shamir's Secret Sharing +# This example demonstrates how to create a threshold signature scheme +# where a subset of signers can create a valid signature. + + +def main() -> None: + # msg = "some message".encode() + N = 3 # Number of signers + T = 2 # Threshold for aggregation + + msk = PrivateKey.random() # Master Secret Key + mpk = msk.public_key() + + print(f"Master Secret Key: {msk.raw_bytes().hex()}") + print(f"Master Public Key: {mpk.string()}") + + shares = msk.split(N, T) + + for i, sk in enumerate(shares): + print(f"Private Key Share {i + 1}: {sk.raw_bytes().hex()}") + + +if __name__ == "__main__": + main() diff --git a/pactus/crypto/bls/private_key.py b/pactus/crypto/bls/private_key.py index 48830a6..3027e0c 100644 --- a/pactus/crypto/bls/private_key.py +++ b/pactus/crypto/bls/private_key.py @@ -79,3 +79,30 @@ def public_key(self) -> PublicKey: def sign(self, msg: bytes) -> Signature: point_g1 = sign(self.scalar, msg, ciphersuite=DST) return Signature(point_g1) + + def split(self, n: int, t: int) -> list[PrivateKey]: + if n < t: + msg = f"n must be greater than t: n={n}, t={t}" + raise ValueError(msg) + + if n < 1: + msg = f"n must be greater than 1: n={n}" + raise ValueError(msg) + + # Create the coefficients for the polynomial: c[0] = secret, c[1..t-1] = random private keys + coeffs = [self.scalar] + for _ in range(1, t): + rand_priv = PrivateKey.random() + coeffs.append(rand_priv.scalar) + + # Generate n shares by evaluating the polynomial at x = 1..n + shares = [] + for i in range(1, n + 1): + share_scalar = utils.evaluate_polynomial(coeffs, i, curve_order) + if share_scalar is None: + msg = f"Failed to evaluate polynomial at x={i}" + raise ValueError(msg) + + shares.append(PrivateKey(share_scalar)) + + return shares