diff --git a/bsv/keys.py b/bsv/keys.py index 085d1ec..dc0276d 100644 --- a/bsv/keys.py +++ b/bsv/keys.py @@ -1,5 +1,6 @@ import hashlib import hmac +import os from base64 import b64encode, b64decode from typing import Optional, Union, Callable, Tuple @@ -11,7 +12,7 @@ from .constants import Network, NETWORK_ADDRESS_PREFIX_DICT, NETWORK_WIF_PREFIX_DICT, PUBLIC_KEY_COMPRESSED_PREFIX_LIST from .curve import Point from .curve import curve, curve_multiply as curve_multiply, curve_add as curve_add -from .hash import hash160, hash256, hmac_sha256 +from .hash import hash160, hash256, hmac_sha256, hmac_sha512 from .utils import decode_wif, text_digest, stringify_ecdsa_recoverable, unstringify_ecdsa_recoverable from .utils import deserialize_ecdsa_recoverable, serialize_ecdsa_der from .polynomial import Polynomial, PointInFiniteField, KeyShares @@ -403,13 +404,39 @@ def to_key_shares(self, threshold: int, total_shares: int) -> 'KeyShares': # Generate shares points = [] + used_x_coordinates = set() + + # Cryptographically secure x-coordinate generation for Shamir's Secret Sharing (toKeyShares) + # + # - Each x-coordinate is derived using a master seed (Random(64)) as the HMAC key and a per-attempt counter array as the message. + # - The counter array includes the share index, the attempt number (to handle rare collisions), and 32 bytes of fresh randomness for each attempt. + # - This ensures: + # 1. **Non-determinism**: Each split is unique, even for the same key and parameters, due to the per-attempt randomness. + # 2. **Uniqueness**: x-coordinates are checked for zero and duplication; retry logic ensures no repeats or invalid values. + # 3. **Cryptographic strength**: HMAC-SHA-512 is robust, and combining deterministic and random values protects against RNG compromise or bias. + # 4. **Defensive programming**: Attempts are capped (5 per share) to prevent infinite loops in pathological cases. + # + # This approach is robust against all practical attacks and is suitable for high-security environments where deterministic splits are not desired. + + seed = os.urandom(64) + for i in range(total_shares): - # Generate random x coordinate using a new private key - # Using private_key.key.to_int() based on the structure in keys.py - random_private_key = PrivateKey() - x = random_private_key.int() + x = None + attempts = 0 + + # TypeScript版と同様の安全なx座標生成 + while x is None or x == 0 or x in used_x_coordinates: + counter = [i, attempts] + list(os.urandom(32)) + counter_bytes = bytes(counter) + + h = hmac_sha512(seed, counter_bytes) + x = int.from_bytes(h, 'big') % curve.p + + attempts += 1 + if attempts > 5: + raise ValueError('Failed to generate unique x coordinate after 5 attempts') - # Evaluate polynomial at x to get y coordinate + used_x_coordinates.add(x) y = poly.value_at(x) # Create a point and add to points' list