Docs
/api
/Types

Types

SDK data structures and type definitions

Type Definitions

Common data structures and type definitions used throughout the Veil SDK.

Core Types

PrivateTransaction

The primary result type returned by all transaction operations.

@dataclass
class PrivateTransaction:
    signature: str
    status: TransactionStatus
    commitment: Optional[str] = None
    nullifier: Optional[str] = None
    proof: Optional[bytes] = None
    secret: Optional[str] = None
    recipient_secret: Optional[str] = None
    leaf_index: Optional[int] = None
 
    def to_dict() -> dict[str, Any]

Fields:

FieldTypeDescription
signaturestrTransaction signature (or "offline" for sync operations)
statusTransactionStatusTransaction status (PENDING, CONFIRMED, FAILED)
commitmentOptional[str]Hex-encoded Pedersen commitment (32 bytes)
nullifierOptional[str]Hex-encoded nullifier (32 bytes, for transfer/unshield)
proofOptional[bytes]zkSNARK proof bytes (256 bytes for Groth16)
secretOptional[str]Secret used to generate commitment (shield operations)
recipient_secretOptional[str]Secret for recipient to spend (transfer operations only)
leaf_indexOptional[int]Merkle tree leaf index (required for spending)

Methods:

def to_dict() -> dict[str, Any]:
    """Convert to dictionary for serialization."""
    return {
        "signature": self.signature,
        "status": self.status.value,
        "commitment": self.commitment,
        "nullifier": self.nullifier,
        "proof": self.proof.hex() if self.proof else None,
        "secret": self.secret,
        "recipient_secret": self.recipient_secret,
        "leaf_index": self.leaf_index
    }

Example:

from veil import VeilClient, generate_secret
from solders.keypair import Keypair
 
async def example():
    client = VeilClient()
    payer = Keypair()
    secret = generate_secret()
 
    # Shield operation
    tx = await client.shield_async(
        amount=1_000_000_000,
        token="SOL",
        keypair=payer,
        secret=secret
    )
 
    print(f"Type: {type(tx)}")  # <class 'PrivateTransaction'>
    print(f"Signature: {tx.signature}")
    print(f"Status: {tx.status}")  # TransactionStatus.CONFIRMED
    print(f"Commitment: {tx.commitment}")
    print(f"Secret: {tx.secret}")
    print(f"Leaf Index: {tx.leaf_index}")
 
    # Nullifier only present for transfers/unshields
    print(f"Nullifier: {tx.nullifier}")  # None for shield
 
    # Convert to dict for storage
    tx_dict = tx.to_dict()
    # Save to database, JSON file, etc.
 
    await client.close()

Field Usage by Operation:

Operationcommitmentnullifiersecretrecipient_secretleaf_index
shield_async()
private_transfer_async()✅ (new)
unshield_async()

TransactionStatus

Enum representing transaction lifecycle states.

class TransactionStatus(Enum):
    PENDING = "pending"
    CONFIRMED = "confirmed"
    FAILED = "failed"

States:

StatusDescriptionWhen Set
PENDINGTransaction created but not submittedSync methods (shield(), private_transfer(), unshield())
CONFIRMEDTransaction confirmed on-chainAsync methods after blockchain confirmation
FAILEDTransaction failed during submissionAsync methods if transaction rejected

Example:

from veil import VeilClient, TransactionStatus
 
# Sync operation - status is PENDING
tx_offline = client.shield(
    amount=1_000_000_000,
    token="SOL",
    secret="my_secret"
)
assert tx_offline.status == TransactionStatus.PENDING
print("Transaction not submitted to blockchain")
 
# Async operation - status is CONFIRMED
tx_online = await client.shield_async(
    amount=1_000_000_000,
    token="SOL",
    keypair=payer,
    secret="my_secret"
)
assert tx_online.status == TransactionStatus.CONFIRMED
print("Transaction confirmed on-chain")
 
# Check status
if tx.status == TransactionStatus.CONFIRMED:
    print(f"Transaction successful: {tx.signature}")
elif tx.status == TransactionStatus.PENDING:
    print("Transaction pending submission")
elif tx.status == TransactionStatus.FAILED:
    print("Transaction failed")

Request Types

ShieldRequest

Request object for shielding assets (internal use).

@dataclass
class ShieldRequest:
    amount: int
    token: str
    owner_secret: str
 
    def validate() -> None:
        """Validate request parameters."""
        if self.amount <= 0:
            raise ValueError("Amount must be positive")
        if len(self.owner_secret) < 32:
            raise ValueError("Secret must be at least 32 characters")

Fields:

  • amount - Amount to shield in smallest unit (lamports for SOL)
  • token - Token address or "SOL" for native SOL
  • owner_secret - Owner's secret key (min 32 characters)

Example:

from veil.types import ShieldRequest
 
request = ShieldRequest(
    amount=1_000_000_000,
    token="SOL",
    owner_secret=generate_secret()
)
 
request.validate()  # Raises ValueError if invalid

TransferRequest

Request object for private transfers (internal use).

@dataclass
class TransferRequest:
    recipient: str
    amount: int
    sender_secret: str
    sender_commitment: str
 
    def validate() -> None:
        """Validate request parameters."""
        if self.amount <= 0:
            raise ValueError("Amount must be positive")
        if not validate_solana_address(self.recipient):
            raise ValueError("Invalid recipient address")

Fields:

  • recipient - Recipient's Solana address (base58)
  • amount - Amount to transfer in lamports
  • sender_secret - Sender's secret key
  • sender_commitment - Sender's commitment (hex string)

Example:

from veil.types import TransferRequest
 
request = TransferRequest(
    recipient="RecipientPublicKey...",
    amount=500_000_000,
    sender_secret="my_secret",
    sender_commitment="abc123..."
)
 
request.validate()  # Raises ValueError if invalid

UnshieldRequest

Request object for unshielding assets (internal use).

@dataclass
class UnshieldRequest:
    amount: int
    destination: str
    owner_secret: str
    commitment: str
 
    def validate() -> None:
        """Validate request parameters."""
        if self.amount <= 0:
            raise ValueError("Amount must be positive")
        if not validate_solana_address(self.destination):
            raise ValueError("Invalid destination address")

Fields:

  • amount - Amount to unshield in lamports
  • destination - Destination public Solana address (base58)
  • owner_secret - Owner's secret key
  • commitment - Commitment to unshield (hex string)

Example:

from veil.types import UnshieldRequest
 
request = UnshieldRequest(
    amount=500_000_000,
    destination="MyPublicWallet...",
    owner_secret="my_secret",
    commitment="commitment_hex..."
)
 
request.validate()  # Raises ValueError if invalid

Cryptographic Types

CommitmentData

Pedersen commitment data with metadata.

@dataclass
class CommitmentData:
    commitment: bytes
    amount: int
    blinding_factor: Optional[bytes] = None
 
    def to_hex() -> str:
        """Convert commitment to hex string."""
        return self.commitment.hex()
 
    @classmethod
    def from_hex(cls, hex_str: str, amount: int) -> CommitmentData:
        """Create from hex string."""
        return cls(
            commitment=bytes.fromhex(hex_str),
            amount=amount
        )

Fields:

  • commitment - 32-byte Pedersen commitment on BN254 G1
  • amount - Amount committed to (in lamports)
  • blinding_factor - 32-byte random blinding factor (optional)

Methods:

# Convert to hex
hex_str = commitment_data.to_hex()
print(f"Commitment: {hex_str}")
 
# Create from hex
commitment_data = CommitmentData.from_hex(
    hex_str="abc123...",
    amount=1_000_000_000
)

Example:

from veil import VeilClient
from veil.types import CommitmentData
 
client = VeilClient()
 
# Generate commitment
tx = client.shield(
    amount=1_000_000_000,
    token="SOL",
    secret="my_secret"
)
 
# Create CommitmentData
commitment_data = CommitmentData.from_hex(
    hex_str=tx.commitment,
    amount=1_000_000_000
)
 
print(f"Commitment: {commitment_data.commitment.hex()}")
print(f"Amount: {commitment_data.amount}")
 
# Convert back to hex
hex_commitment = commitment_data.to_hex()

EncryptedNote

ECDH-encrypted note for recipient discovery.

@dataclass
class EncryptedNote:
    ciphertext: bytes           # ChaCha20-Poly1305 encrypted note data
    ephemeral_pubkey: bytes     # Ephemeral ECDH public key (32 bytes)
    mac: bytes                  # Poly1305 MAC tag (16 bytes)
 
    def __len__(self) -> int:
        """Total size: 96 bytes"""
        return len(self.ciphertext) + len(self.ephemeral_pubkey) + len(self.mac)

Fields:

  • ciphertext - Encrypted note data (48 bytes):
    • amount (8 bytes)
    • blinding (32 bytes)
    • asset_id (8 bytes)
  • ephemeral_pubkey - Ephemeral ECDH public key (32 bytes)
  • mac - Poly1305 authentication tag (16 bytes)

Total size: 96 bytes (48 + 32 + 16)

Example:

from veil.types import EncryptedNote
 
# Received from on-chain transaction logs
encrypted_note = EncryptedNote(
    ciphertext=bytes.fromhex("..."),  # 48 bytes
    ephemeral_pubkey=bytes.fromhex("..."),  # 32 bytes
    mac=bytes.fromhex("...")  # 16 bytes
)
 
print(f"Encrypted note size: {len(encrypted_note)} bytes")  # 96
print(f"Ciphertext: {encrypted_note.ciphertext.hex()}")
print(f"Ephemeral pubkey: {encrypted_note.ephemeral_pubkey.hex()}")
 
# Try to decrypt (trial decryption)
try:
    note_data = decrypt_note(encrypted_note, my_secret_key)
    print(f"Note is for me! Amount: {note_data['amount']}")
except:
    print("Note is not for me (decryption failed)")

Note

Decrypted note data (private, local only).

@dataclass
class Note:
    amount: int
    blinding: bytes
    asset_id: int
    commitment: bytes
    leaf_index: int
    secret: str

Fields:

  • amount - Note amount in smallest unit (8 bytes)
  • blinding - Random blinding factor (32 bytes)
  • asset_id - Asset identifier (0 = SOL, others = SPL tokens) (8 bytes)
  • commitment - Pedersen commitment (32 bytes, derived)
  • leaf_index - Merkle tree leaf index
  • secret - Secret key to spend this note

Example:

from veil.types import Note
 
# Save note locally after shield/transfer
note = Note(
    amount=1_000_000_000,
    blinding=blinding_factor,
    asset_id=0,  # SOL
    commitment=tx.commitment,
    leaf_index=tx.leaf_index,
    secret=tx.secret
)
 
# Store in database
notes_db = {
    "unspent": [note],
    "spent": []
}
 
# Spend note later
tx = await client.private_transfer_async(
    input_note_secret=note.secret,
    input_note_index=note.leaf_index,
    recipient_pubkey="...",
    amount=note.amount,
    keypair=payer
)
 
# Mark as spent
notes_db["spent"].append(note)
notes_db["unspent"].remove(note)

Proof Types

Groth16Proof

Groth16 zkSNARK proof structure (internal representation).

@dataclass
class Groth16Proof:
    proof_a: bytes  # G1 point (64 bytes)
    proof_b: bytes  # G2 point (128 bytes)
    proof_c: bytes  # G1 point (64 bytes)
 
    def to_bytes(self) -> bytes:
        """Serialize to 256 bytes."""
        return self.proof_a + self.proof_b + self.proof_c
 
    @classmethod
    def from_bytes(cls, data: bytes) -> Groth16Proof:
        """Deserialize from 256 bytes."""
        if len(data) != 256:
            raise ValueError("Proof must be 256 bytes")
        return cls(
            proof_a=data[0:64],
            proof_b=data[64:192],
            proof_c=data[192:256]
        )

Fields:

  • proof_a - G1 curve point (64 bytes, compressed)
  • proof_b - G2 curve point (128 bytes, compressed)
  • proof_c - G1 curve point (64 bytes, compressed)

Total size: 256 bytes

Example:

from veil.types import Groth16Proof
 
# From PrivateTransaction
proof_bytes = tx.proof  # 256 bytes
 
# Parse proof structure
proof = Groth16Proof.from_bytes(proof_bytes)
print(f"Proof A: {proof.proof_a.hex()}")
print(f"Proof B: {proof.proof_b.hex()}")
print(f"Proof C: {proof.proof_c.hex()}")
 
# Serialize back
serialized = proof.to_bytes()
assert serialized == proof_bytes

Utility Types

MerkleProof

Merkle proof for commitment membership.

@dataclass
class MerkleProof:
    path: list[bytes]  # 20 sibling hashes (depth 20)
    leaf_index: int
    root: bytes  # 32-byte Merkle root
 
    def verify(self, leaf: bytes) -> bool:
        """Verify proof is valid for leaf."""
        # Implementation in Rust core

Fields:

  • path - List of 20 sibling hashes (32 bytes each) for Merkle path
  • leaf_index - Index of the leaf being proven (0 to ~1M)
  • root - Expected Merkle root (32 bytes)

Example:

from veil import VeilClient
 
async def get_merkle_proof_example():
    client = VeilClient()
 
    # Get proof for a commitment
    commitment_bytes = bytes.fromhex(tx.commitment)
    proof = await client.get_merkle_proof(
        commitment=commitment_bytes,
        leaf_index=tx.leaf_index
    )
 
    print(f"Merkle path length: {len(proof.path)}")  # 20
    print(f"Leaf index: {proof.leaf_index}")
    print(f"Root: {proof.root.hex()}")
 
    # Verify proof
    is_valid = proof.verify(commitment_bytes)
    print(f"Proof valid: {is_valid}")
 
    await client.close()

Type Conversion Utilities

commitment_to_hex()

Convert commitment bytes to hex string.

def commitment_to_hex(commitment: bytes) -> str:
    """
    Convert 32-byte commitment to hex string.
 
    Args:
        commitment: 32-byte commitment
 
    Returns:
        Hex string (64 characters)
    """
    if len(commitment) != 32:
        raise ValueError("Commitment must be 32 bytes")
    return commitment.hex()

Example:

from veil import commitment_to_hex
 
commitment_bytes = b'\xab\xcd...'  # 32 bytes
hex_str = commitment_to_hex(commitment_bytes)
print(hex_str)  # "abcd..."

hex_to_bytes()

Convert hex string to bytes.

def hex_to_bytes(hex_str: str) -> bytes:
    """
    Convert hex string to bytes.
 
    Args:
        hex_str: Hex string (with or without "0x" prefix)
 
    Returns:
        Bytes
    """
    # Remove "0x" prefix if present
    if hex_str.startswith("0x"):
        hex_str = hex_str[2:]
    return bytes.fromhex(hex_str)

Example:

from veil import hex_to_bytes
 
# With 0x prefix
bytes1 = hex_to_bytes("0xabcd1234")
 
# Without prefix
bytes2 = hex_to_bytes("abcd1234")
 
assert bytes1 == bytes2

validate_solana_address()

Validate Solana address format.

def validate_solana_address(address: str) -> bool:
    """
    Validate Solana address (base58 format).
 
    Args:
        address: Solana address string
 
    Returns:
        True if valid, False otherwise
    """
    try:
        from solders.pubkey import Pubkey
        Pubkey.from_string(address)
        return True
    except:
        return False

Example:

from veil import validate_solana_address
 
# Valid address
is_valid = validate_solana_address("11111111111111111111111111111111")
print(is_valid)  # True
 
# Invalid address
is_valid = validate_solana_address("not_an_address")
print(is_valid)  # False
 
# Use in validation
recipient = "RecipientPublicKey..."
if not validate_solana_address(recipient):
    raise ValueError(f"Invalid recipient address: {recipient}")

generate_secret()

Generate cryptographically secure secret.

def generate_secret(length: int = 32) -> str:
    """
    Generate cryptographically secure random secret.
 
    Args:
        length: Length in bytes (default: 32)
 
    Returns:
        Hex-encoded secret string (2*length characters)
    """
    import secrets
    return secrets.token_hex(length)

Example:

from veil import generate_secret
 
# Generate 32-byte secret (64 hex characters)
secret = generate_secret()
print(f"Secret: {secret}")
print(f"Length: {len(secret)} chars")  # 64
 
# Generate custom length
secret_16 = generate_secret(length=16)
print(f"16-byte secret: {secret_16}")  # 32 hex characters
 
# Use in shield operation
tx = await client.shield_async(
    amount=1_000_000_000,
    token="SOL",
    keypair=payer,
    secret=generate_secret()  # Auto-generate
)

Type Aliases

Common type aliases used throughout the SDK:

# Addresses
Address = str  # Base58-encoded Solana address
 
# Cryptographic primitives
Hash = bytes  # 32-byte hash
Commitment = bytes  # 32-byte Pedersen commitment
Nullifier = bytes  # 32-byte nullifier
Secret = str  # Hex-encoded secret (min 32 chars)
 
# Amounts
Lamports = int  # Amount in lamports (1 SOL = 1e9 lamports)
Tokens = int  # Amount in token smallest unit
 
# Proofs
ProofBytes = bytes  # 256-byte Groth16 proof

Complete Type Usage Example

Full example showing all major types in action:

import asyncio
from solders.keypair import Keypair
from veil import (
    VeilClient,
    generate_secret,
    PrivateTransaction,
    TransactionStatus,
    validate_solana_address
)
 
async def complete_type_example():
    """Demonstrate all major types."""
    client = VeilClient()
    payer = Keypair()
 
    # 1. Generate secret (utility function)
    secret = generate_secret()
    print(f"Generated secret: {secret[:16]}...")
 
    # 2. Shield operation (returns PrivateTransaction)
    shield_tx: PrivateTransaction = await client.shield_async(
        amount=1_000_000_000,
        token="SOL",
        keypair=payer,
        secret=secret
    )
 
    print(f"\nShield Transaction:")
    print(f"  Type: {type(shield_tx)}")
    print(f"  Status: {shield_tx.status}")  # TransactionStatus.CONFIRMED
    print(f"  Commitment: {shield_tx.commitment}")
    print(f"  Secret: {shield_tx.secret}")
    print(f"  Leaf Index: {shield_tx.leaf_index}")
 
    # 3. Validate recipient address (utility function)
    recipient = "RecipientPublicKey..."
    if not validate_solana_address(recipient):
        raise ValueError("Invalid recipient")
 
    # 4. Private transfer (returns PrivateTransaction with different fields)
    transfer_tx: PrivateTransaction = await client.private_transfer_async(
        input_note_secret=shield_tx.secret,
        input_note_index=shield_tx.leaf_index,
        recipient_pubkey=recipient,
        amount=500_000_000,
        keypair=payer
    )
 
    print(f"\nTransfer Transaction:")
    print(f"  Status: {transfer_tx.status}")
    print(f"  Nullifier: {transfer_tx.nullifier}")  # Now present!
    print(f"  New Commitment: {transfer_tx.commitment}")
    print(f"  Recipient Secret: {transfer_tx.recipient_secret}")  # Share with recipient
    print(f"  Proof size: {len(transfer_tx.proof)} bytes")  # 256 bytes
 
    # 5. Convert to dict for storage
    tx_dict = transfer_tx.to_dict()
    print(f"\nAs dict: {list(tx_dict.keys())}")
 
    await client.close()
 
# Run
asyncio.run(complete_type_example())

See Also