Types
SDK data structures and type definitions
Common data structures and type definitions used throughout the Veil SDK.
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:
| Field | Type | Description |
|---|---|---|
signature | str | Transaction signature (or "offline" for sync operations) |
status | TransactionStatus | Transaction status (PENDING, CONFIRMED, FAILED) |
commitment | Optional[str] | Hex-encoded Pedersen commitment (32 bytes) |
nullifier | Optional[str] | Hex-encoded nullifier (32 bytes, for transfer/unshield) |
proof | Optional[bytes] | zkSNARK proof bytes (256 bytes for Groth16) |
secret | Optional[str] | Secret used to generate commitment (shield operations) |
recipient_secret | Optional[str] | Secret for recipient to spend (transfer operations only) |
leaf_index | Optional[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:
| Operation | commitment | nullifier | secret | recipient_secret | leaf_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:
| Status | Description | When Set |
|---|---|---|
PENDING | Transaction created but not submitted | Sync methods (shield(), private_transfer(), unshield()) |
CONFIRMED | Transaction confirmed on-chain | Async methods after blockchain confirmation |
FAILED | Transaction failed during submission | Async 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")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 SOLowner_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 invalidTransferRequest
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 lamportssender_secret- Sender's secret keysender_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 invalidUnshieldRequest
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 lamportsdestination- Destination public Solana address (base58)owner_secret- Owner's secret keycommitment- 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 invalidCommitmentData
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 G1amount- 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: strFields:
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 indexsecret- 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)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_bytesMerkleProof
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 coreFields:
path- List of 20 sibling hashes (32 bytes each) for Merkle pathleaf_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()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 == bytes2validate_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 FalseExample:
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
)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 proofFull 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())- VeilClient - Main SDK client API
- RelayerClient - Relayer API
- Crypto - Low-level cryptographic functions
- Quick Start - Usage examples
On This Page
- Type Definitions
- Core Types
- PrivateTransaction
- TransactionStatus
- Request Types
- ShieldRequest
- TransferRequest
- UnshieldRequest
- Cryptographic Types
- CommitmentData
- EncryptedNote
- Note
- Proof Types
- Groth16Proof
- Utility Types
- MerkleProof
- Type Conversion Utilities
- commitment_to_hex()
- hex_to_bytes()
- validate_solana_address()
- generate_secret()
- Type Aliases
- Complete Type Usage Example
- See Also