Source code for rizemind.authentication.signatures.signature

from typing import Self

from eth_typing import HexStr
from pydantic import BaseModel, Field, field_validator
from web3 import Web3


[docs] class Signature(BaseModel): """Represents an ECDSA signature for Ethereum. This model provides a structured way to handle the 65-byte signature format, which is a concatenation of the r, s, and v components. It includes properties to access individual components and class methods for convenient instantiation from different formats. Attributes: data: The raw 65-byte signature, concatenated as r (32 bytes) + s (32 bytes) + v (1 byte). """ data: bytes = Field(..., description="65-byte signature (r + s + v)")
[docs] @field_validator("data") @classmethod def validate_signature_length(cls, v: bytes) -> bytes: """Pydantic validator to ensure the signature data is exactly 65 bytes long. Args: v: The input byte string to validate. Returns: The validated 65-byte string. Raises: ValueError: If the length of `v` is not 65. """ if len(v) != 65: raise ValueError("Signature must be exactly 65 bytes") return v
@property def r(self) -> HexStr: """The 'r' value of the ECDSA signature. Returns: The first 32 bytes of the signature as a `HexStr`. """ return Web3.to_hex(self.data[:32]) @property def s(self) -> HexStr: """The 's' value of the ECDSA signature. Returns: The middle 32 bytes (bytes 32-64) of the signature as a `HexStr`. """ return Web3.to_hex(self.data[32:64]) @property def v(self) -> int: """The 'v' value (recovery identifier) of the ECDSA signature. Returns: The last byte (65th) of the signature as an integer. """ return self.data[64]
[docs] @classmethod def from_hex(cls, signature: HexStr) -> Self: """Creates a `Signature` instance from a hexadecimal string. Args: signature: The 65-byte signature as a hex string (e.g., '0x...'). Returns: A new `Signature` instance. """ return cls(data=Web3.to_bytes(hexstr=signature))
[docs] @classmethod def from_rsv(cls, r: HexStr, s: HexStr, v: int) -> Self: """Creates a `Signature` instance from its r, s, and v components. Args: r: The 'r' value as a 32-byte hex string. s: The 's' value as a 32-byte hex string. v: The 'v' value (recovery ID), must be 27 or 28. Returns: A new `Signature` instance. Raises: ValueError: If `v` is not 27 or 28, or if `r` or `s` are not 32 bytes each after conversion from hex. """ if v not in (27, 28): raise ValueError("v must be either 27 or 28") r_bytes = Web3.to_bytes(hexstr=r) s_bytes = Web3.to_bytes(hexstr=s) if len(r_bytes) != 32 or len(s_bytes) != 32: raise ValueError("r and s must be 32 bytes each") return cls(data=r_bytes + s_bytes + bytes([v]))
[docs] def to_tuple(self) -> tuple[int, bytes, bytes]: """Converts the signature to a tuple of (v, r, s). This format is commonly used by Ethereum libraries like `eth-account` for transaction signing and public key recovery. Returns: A tuple containing the signature components: (v, r_bytes, s_bytes). """ return ( self.v, self.data[:32], # r self.data[32:64], # s )
[docs] def to_hex(self) -> HexStr: """The full 65-byte signature as a hex string. Returns: The signature as a `HexStr` (e.g., '0x...'). """ return Web3.to_hex(self.data)
def __str__(self) -> str: """The string representation of the signature, which is its hex format. Returns: The signature as a hex string. """ return self.to_hex()