Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Welcome to the BSV Blockchain Libraries Project, the comprehensive Python SDK de
2. [Getting Started](#getting-started)
3. [Features & Deliverables](#features--deliverables)
4. [Documentation](#documentation)
5. [Tutorial](#Tutorial)
5. [Contribution Guidelines](#contribution-guidelines)
6. [Support & Contacts](#support--contacts)

Expand Down Expand Up @@ -111,6 +112,11 @@ Detailed documentation of the SDK with code examples can be found at [BSV Skills
You can also refer to the [User Test Report](./docs/Py-SDK%20User%20Test%20Report.pdf) for insights and feedback provided by
[Yenpoint](https://yenpoint.jp/).

## Beginner Tutorial
#### [Step-by-Step BSV Tutorial: Sending BSV and NFTs](./docs/beginner_tutorial.md)

This beginner-friendly guide will walk you through sending BSV (Bitcoin SV) and creating NFTs using the BSV Python SDK. We'll take it step-by-step so you can learn at your own pace.

## Contribution Guidelines

We're always looking for contributors to help us improve the project. Whether it's bug reports, feature requests, or pull requests - all
Expand Down
2 changes: 1 addition & 1 deletion bsv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@
from .signed_message import *


__version__ = '1.0.3'
__version__ = '1.0.5'
128 changes: 127 additions & 1 deletion bsv/keys.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import tempfile
import hashlib
import hmac
from base64 import b64encode, b64decode
Expand All @@ -15,6 +14,7 @@
from .hash import hash160, hash256, hmac_sha256
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


class PublicKey:
Expand Down Expand Up @@ -373,6 +373,132 @@ def from_pem(cls, octets: Union[str, bytes]) -> 'PrivateKey': # pragma: no cove
b: bytes = octets if isinstance(octets, bytes) else bytes.fromhex(octets)
return PrivateKey(CcPrivateKey.from_pem(b))

def to_key_shares(self, threshold: int, total_shares: int) -> 'KeyShares':
"""
Split the private key into shares using Shamir's Secret Sharing Scheme.

Args:
threshold: The minimum number of shares required to reconstruct the private key
total_shares: The total number of shares to generate

Returns:
A KeyShares object containing the generated shares

Raises:
ValueError: If threshold or total_shares are invalid
"""

# Input validation
if not isinstance(threshold, int) or not isinstance(total_shares, int):
raise ValueError("threshold and totalShares must be numbers")
if threshold < 2:
raise ValueError("threshold must be at least 2")
if total_shares < 2:
raise ValueError("totalShares must be at least 2")
if threshold > total_shares:
raise ValueError("threshold should be less than or equal to totalShares")

# Create polynomial from private key
poly = Polynomial.from_private_key(self.int(), threshold)

# Generate shares
points = []
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()

# Evaluate polynomial at x to get y coordinate
y = poly.value_at(x)

# Create a point and add to points' list
points.append(PointInFiniteField(x, y))

# Calculate integrity hash from the public key
# In the JS implementation: (this.toPublicKey().toHash('hex') as string).slice(0, 8)
integrity = self.public_key().hash160().hex()[:8]

return KeyShares(points, threshold, integrity)

def to_backup_shares(self, threshold: int, total_shares: int) -> list:
"""
Creates a backup of the private key by splitting it into shares.

Args:
threshold: The number of shares which will be required to reconstruct the private key
total_shares: The number of shares to generate for distribution

Returns:
List of share strings in backup format
"""
key_shares = self.to_key_shares(threshold, total_shares)
return key_shares.to_backup_format()

@staticmethod
def from_backup_shares(shares: list) -> 'PrivateKey':
"""
Reconstructs a private key from backup shares.

Args:
shares: List of share strings in backup format

Returns:
The reconstructed PrivateKey object

Raises:
ValueError: If shares are invalid or inconsistent
"""
return PrivateKey.from_key_shares(KeyShares.from_backup_format(shares))

@staticmethod
def from_key_shares(key_shares: 'KeyShares') -> 'PrivateKey':
"""
Combines shares to reconstruct the private key.

Args:
key_shares: A KeyShares object containing the shares

Returns:
The reconstructed PrivateKey object

Raises:
ValueError: If not enough shares are provided or shares are invalid
"""


points = key_shares.points
threshold = key_shares.threshold
integrity = key_shares.integrity

# Validate inputs
if threshold < 2:
raise ValueError("threshold must be at least 2")
if len(points) < threshold:
raise ValueError(f"At least {threshold} shares are required to reconstruct the private key")

# Check for duplicate x values
for i in range(threshold):
for j in range(i + 1, threshold):
if points[i].x == points[j].x:
raise ValueError("Duplicate share detected, each must be unique.")

# Create polynomial from points
poly = Polynomial(points[:threshold], threshold)

# Evaluate polynomial at x=0 to get the private key
secret_value = poly.value_at(0)

# Create private key from secret value
# Instead of from_int (which doesn't exist), use the proper constructor
private_key = PrivateKey(secret_value)

# Verify integrity by comparing hash of public key
reconstructed_integrity = private_key.public_key().hash160().hex()[:8]
if reconstructed_integrity != integrity:
raise ValueError("Integrity hash mismatch")

return private_key

def verify_signed_text(
text: str,
Expand Down
Loading