Skip to main content

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​

AspectOn-ChainClient-Side
ValidatorAll nodesOnly parties involved
PrivacyPublicPrivate
ScalabilityLimitedMassive
State StorageBlockchainOff-chain
Validation CostNetwork-wideIndividual
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:

  1. UTXO spending - UTXOs can only be spent once (standard Bitcoin property)
  2. Bitcoin confirmation - UTXO spend is final
  3. 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​

Additional Resources​