Source code for secretvaults.common.keypair

"""
Cryptographic keypair utilities for SecretVaults.

This module provides secp256k1-based keypair functionality including
key generation, signing, and DID (Decentralized Identifier) creation.
"""

import os
import hashlib
from binascii import unhexlify
from secp256k1 import PrivateKey as Secp256k1PrivateKey, PublicKey as Secp256k1PublicKey
from nuc.token import Did


[docs] class Keypair: """A cryptographic keypair for SecretVaults operations. Provides secp256k1-based keypair functionality with methods for signing, DID generation, and key management. """ def __init__(self, privkey_bytes: bytes): """Initialize a keypair from private key bytes. Args: privkey_bytes: 32-byte private key Raises: ValueError: If private key is not exactly 32 bytes """ if len(privkey_bytes) != 32: raise ValueError("Private key must be 32 bytes") # Store as secp256k1.PrivateKey instance self._private_key = Secp256k1PrivateKey(privkey=privkey_bytes, raw=True) self._public_key = self._private_key.pubkey # secp256k1.PublicKey instance
[docs] @classmethod def from_hex(cls, hex_key: str) -> "Keypair": """Create a keypair from a hexadecimal private key string. Args: hex_key: Private key as hex string Returns: Keypair instance """ return cls(unhexlify(hex_key))
[docs] @classmethod def from_bytes(cls, key_bytes: bytes) -> "Keypair": """Create a keypair from private key bytes. Args: key_bytes: Private key as bytes Returns: Keypair instance """ return cls(key_bytes)
[docs] @classmethod def generate(cls) -> "Keypair": """Generate a new random keypair. Returns: Keypair instance with cryptographically secure random private key """ return cls(os.urandom(32))
[docs] def private_key(self) -> Secp256k1PrivateKey: """Get the secp256k1 private key object. Returns: secp256k1.PrivateKey instance """ return self._private_key
[docs] def public_key(self) -> Secp256k1PublicKey: """Get the secp256k1 public key object. Returns: secp256k1.PublicKey instance """ return self._public_key
[docs] def private_key_hex(self) -> str: """Get the private key as a hexadecimal string. Returns: Private key as hex string """ return self._private_key.private_key.hex()
[docs] def public_key_hex(self, compressed=False) -> str: """Get the public key as a hexadecimal string. Args: compressed: Whether to return compressed format Returns: Public key as hex string """ return self._public_key.serialize(compressed=compressed).hex()
[docs] def matches_public_key(self, pk: bytes | str) -> bool: """Check if this keypair's public key matches the given key. Args: pk: Public key as bytes or hex string Returns: True if public keys match, False otherwise """ ref = pk.hex() if isinstance(pk, bytes) else pk return self.public_key_hex() == ref
[docs] def to_did(self) -> Did: """Create a DID (Decentralized Identifier) from the public key. Returns: Did object representing this keypair's identity """ pubkey_bytes = self._public_key.serialize(compressed=True) return Did(pubkey_bytes)
[docs] def to_did_string(self) -> str: """Get the DID as a string representation. Returns: DID string in format 'did:nil:<pubkey_hex>' """ return str(self.to_did())
[docs] def sign(self, msg: str, fmt: str = "hex") -> bytes | str: """Sign a message using ECDSA with secp256k1. Args: msg: Message to sign fmt: Output format - "hex" for hex string, "bytes" for bytes Returns: Signature as hex string or bytes """ msg_bytes = msg.encode() digest = hashlib.sha256(msg_bytes).digest() sig_obj = self._private_key.ecdsa_sign_recoverable(digest) sig_bytes, rec_id = self._private_key.ecdsa_recoverable_serialize(sig_obj) full_sig = sig_bytes + bytes([rec_id]) return full_sig if fmt == "bytes" else full_sig.hex()