Client-Side Validation
Client-side validation is the foundational concept that makes RGB unique. It fundamentally reverses how blockchain systems work by moving validation from the network to the client.
The Paradigm Shift
Traditional Blockchain Validation
Every node validates every transaction
↓
Global state replication
↓
Limited scalability
↓
No privacy
RGB Client-Side Validation
Only involved parties validate
↓
Partial state replication
↓
Massive scalability
↓
Strong privacy
How It Works
1. State Ownership
In RGB, you only need to know about state that belongs to you:
// Alice doesn't know about Bob's assets
// Bob doesn't know about Charlie's assets
// Each validates only what they own
2. Consignment Transfer
When transferring assets, the sender provides a consignment - a data package containing all validation data:
Sender creates consignment containing:
├── Asset origin (genesis)
├── Full ownership history
├── State transitions
├── Witness data
└── Bitcoin commitments
3. Recipient Validation
The recipient validates the consignment independently:
// Pseudocode validation flow
fn validate_consignment(consignment: Consignment) -> Result<()> {
// 1. Verify genesis is valid
verify_genesis(&consignment.genesis)?;
// 2. Verify all state transitions
for transition in &consignment.transitions {
verify_transition(transition)?;
verify_bitcoin_commitment(transition)?;
}
// 3. Verify UTXOs are properly spent
verify_utxos_spent(&consignment)?;
// 4. Verify I'm the recipient
verify_ownership(&consignment, my_utxo)?;
Ok(())
}
Benefits
Privacy
Since only involved parties see the data:
- No public transaction history
- No public balances
- No public contract state
- Third parties can't track your assets
Scalability
Validation complexity is O(personal history), not O(global state):
Traditional: Validate ALL transactions ever
RGB: Validate only YOUR transaction history
This enables:
- Parallel validation across different assets
- No global state synchronization
- Unlimited number of contracts
Efficiency
Memory usage: Hundreds of bytes (not gigabytes)
Validation time: Milliseconds (not seconds)
Network bandwidth: Minimal
Consignment Structure
Anatomy of a Consignment
struct Consignment {
// Contract origin
genesis: Genesis,
// All state transitions in ownership chain
transitions: Vec<StateTransition>,
// Bitcoin transaction witnesses
witnesses: Vec<BitcoinTx>,
// Extension data (optional)
extensions: Vec<Extension>,
// Terminal states
terminals: Vec<Terminal>,
}
Stream-Based Validation (v0.12)
RGB v0.12 introduced streaming validation:
// Don't load entire consignment into memory
let mut stream = ConsignmentStream::new(file)?;
// Validate on-the-fly
while let Some(chunk) = stream.next()? {
validate_chunk(chunk)?;
}
// Memory usage: ~200 bytes throughout
Validation Rules
1. Genesis Validation
fn validate_genesis(genesis: &Genesis) -> Result<()> {
// Verify schema compliance
verify_schema(&genesis.schema)?;
// Verify initial state allocations
verify_initial_state(&genesis.assignments)?;
// Verify metadata
verify_metadata(&genesis.metadata)?;
Ok(())
}
2. Transition Validation
fn validate_transition(transition: &Transition) -> Result<()> {
// Verify inputs exist and are unspent
verify_inputs(&transition.inputs)?;
// Execute AluVM validation code
execute_validation_script(&transition.script)?;
// Verify state transformation rules
verify_state_change(&transition)?;
// Verify Bitcoin commitment
verify_commitment(&transition.commitment)?;
Ok(())
}
3. UTXO Verification
fn verify_utxos_spent(consignment: &Consignment) -> Result<()> {
for transition in &consignment.transitions {
for utxo in &transition.inputs {
// Verify UTXO was spent (standard Bitcoin validation)
verify_utxo_spent(utxo)?;
// Verify Bitcoin transaction witness
verify_tx_witness(utxo)?;
}
}
Ok(())
}
Client-Side Validation in Practice
Example: Token Transfer
Comparison with On-Chain Validation
| Aspect | On-Chain | Client-Side |
|---|---|---|
| Validator | All nodes | Only parties involved |
| Privacy | Public | Private |
| Scalability | Limited | Massive |
| State Storage | Blockchain | Off-chain |
| Validation Cost | Network-wide | Individual |
| Throughput | ~10 TPS (Bitcoin) | Unlimited |
Advanced Concepts
Proof Compression
Future zk-STARK integration will compress consignments:
Current: ~100 lines of validation code
Future: Single zk-STARK proof
Size: Constant regardless of history length
Partial Disclosure
You can prove specific facts without revealing full history:
// Prove balance without revealing transactions
let proof = create_balance_proof(
balance: 1000,
without_revealing_history: true
);
History Pruning
Consignments can be "merged" to reduce size:
Long history: A → B → C → D → E (5 transitions)
Pruned: A → E (1 transition)
Validity: Same cryptographic guarantees
Implementation Details
Validation Order
// Topological sort of DAG
let ordered = topological_sort(&consignment.transitions);
// Validate in dependency order
for transition in ordered {
validate_transition(transition)?;
}
Caching
// Cache validation results
let mut cache = ValidationCache::new();
// Reuse previous validations
if let Some(result) = cache.get(&transition_id) {
return result;
}
Parallel Validation
// Validate independent branches in parallel
let results = transitions
.par_iter()
.map(|t| validate_transition(t))
.collect::<Result<Vec<_>>>()?;
Security Considerations
Double-Spend Prevention
Client-side validation prevents double-spending by leveraging Bitcoin's UTXO model:
- UTXO spending - UTXOs can only be spent once (standard Bitcoin property)
- Bitcoin confirmation - UTXO spend is final
- Cryptographic commitment - RGB state linked to specific UTXO
Validation Completeness
Recipients must validate:
- ✓ All transitions from genesis
- ✓ All Bitcoin commitments
- ✓ All UTXO spends
- ✓ Schema compliance
- ✓ AluVM execution
Incomplete validation = Risk of invalid assets
Trust Model
Don't trust, verify!
You validate:
├── Full ownership history
├── Bitcoin commitments
├── Cryptographic proofs
└── Smart contract logic
You don't trust:
├── Sender's claims
├── Third party validation
└── Network consensus
Best Practices
For Wallet Developers
// Always validate before accepting
async function acceptTransfer(consignment: Consignment) {
// Validate consignment
await validateConsignment(consignment);
// Check Bitcoin confirmation
await waitForConfirmation(consignment.txid);
// Update local state
await updateBalances();
}
For Users
1. Always wait for Bitcoin confirmation
2. Verify consignment size is reasonable
3. Keep backup of consignment files
4. Validate immediately upon receipt
Common Pitfalls
❌ Trusting Without Validating
// WRONG: Assuming sender is honest
function unsafeAccept(consignment: Consignment) {
// Just accept without validation
updateBalance(+100); // Dangerous!
}
// RIGHT: Always validate
async function safeAccept(consignment: Consignment) {
await fullValidation(consignment);
updateBalance(validated_amount);
}
❌ Partial Validation
// WRONG: Only checking some transitions
function partialValidation(consignment: Consignment) {
validate(consignment.latest); // Incomplete!
}
// RIGHT: Full history validation
function fullValidation(consignment: Consignment) {
validateFromGenesis(consignment); // Complete!
}
Next Steps
- UTXO Binding - How RGB uses Bitcoin's UTXO model
- PRISM Computing - The computational model
- Consignments - Technical specification