← Back to Main Documentation Core Systems Index

Plings Identifier Technical Specification

This document provides the technical implementation specification for Plings identifiers. For understanding the design rationale, use cases, and real-world applications, see Plings Identifier Overview.

Overview

A Plings identifier is a cryptographically-derived unique string that provides:

  • Direct payment capability enabling objects to receive payments without traditional POS systems
  • Native Solana addresses allowing objects to participate directly in blockchain economy
  • Offline verification of authenticity without requiring network access
  • Hierarchical organization supporting manufacturer independence
  • Flexible deployment across QR codes, NFC tags, barcodes, and future mediums
  • Anti-counterfeiting protection through cryptographic proof chains
  • Minimal data footprint through on-demand database storage

Critical Innovation: HD Wallet → Solana Address Mapping

The Revolutionary Breakthrough: Every Plings identifier is simultaneously:

  1. HD wallet path for hierarchical organization and offline verification
  2. Native Solana address capable of receiving payments and executing smart contracts
  3. Object commerce endpoint enabling direct economic transactions
HD Path: "2.G.2.2.7P.9j" + Class Pointer: "4K7mX9abDcE" → Rolex Submariner instance
HD Derivation: m/44'/501'/1'/15'/1'/3'/2024'/158' (Wallet v1, Solana standard)
Result: Ed25519 keypair → Solana address
Instance Key: 8TxW4nZBE3yPcBYGqka29FZkUETQzfma8xUG47jKbLRH (Solana address)
Capability: Object can receive payments, transfer ownership, execute contracts

This dual nature enables objects to sell themselves without any traditional payment infrastructure.

Revolutionary Advancement: Direct Object Commerce

Traditional GS1/Barcode Systems: Objects are passive - they identify products at checkout terminals Plings System: Objects become active commerce endpoints - scan and pay the object directly

This fundamental shift eliminates entire layers of retail infrastructure by enabling:

  • Instant ownership transfer via blockchain NFTs
  • Service marketplace per object (rent, repair, insurance)
  • Vending without machines - any object can sell itself
  • Zero checkout lines - scan, pay, and walk out

Core Technical Principle: Generate Without Storing

Revolutionary Data Architecture

Traditional Identifier Systems:

Manufacturing Process:
1. Request unique ID from central database
2. Database generates and stores new record
3. Return ID to production line
4. Print ID on product
Result: Database grows by 100% of production volume

Plings Cryptographic System:

Manufacturing Process:
1. Generate cryptographic identifier locally using HD wallet
2. Print identifier on product 
3. Product ships - NO DATABASE INTERACTION
4. Consumer scans - FIRST DATABASE ENTRY CREATED
Result: Database grows by ~0.01% of production volume

Technical Implementation

Identifier Generation (No Database Required)

def generate_product_identifier(wallet_master_key: str, path: str) -> str:
    """
    Generate cryptographically unique identifier without database access.
    
    Args:
        wallet_master_key: Wallet master key from HSM (e.g., Wallet v1 key)
        path: Complete path (e.g., "2.3j.4.5Q.3L8z" for Coca-Cola Classic instance) + class pointer
    
    Returns:
        Cryptographically unique identifier that can be verified against wallet
    """
    # Convert path to wallet-aware HD derivation
    # Example: "2.3j.4.5Q.3L8z" → "m/44'/501'/1'/3'/1255'/2'/5847'/90000'" (class pointer separate)
    hd_derivation = path_to_wallet_hd_derivation(path, wallet_version=1)
    
    # Derive instance key from wallet master key + HD path
    # CRITICAL: instance_key IS the Solana address (Base58-encoded Ed25519 public key)
    # This enables direct payments to the object without any intermediary
    instance_key = derive_hd_key(wallet_master_key, hd_derivation)  # Returns Solana address
    
    # Generate class pointer from manufacturer pubkey + path_to_pointer
    path_to_pointer = extract_path_to_pointer(path)  # Everything before batch.instance
    class_pointer = generate_class_pointer(manufacturer_pubkey, path_to_pointer)
    
    # Generate URL with cryptographic proof (class pointer separate)
    return f"https://s.plings.io?t=q&p={path}&cp={class_pointer}&i={instance_key}"
    
    # NO DATABASE CALL REQUIRED
    # NO NETWORK CONNECTION NEEDED
    # GUARANTEED UNIQUE THROUGH CRYPTOGRAPHY
    # WALLET VERSION EMBEDDED IN DERIVATION

Database Entry Creation (On-Demand Only)

def handle_first_scan(identifier_url: str, scan_context: dict) -> dict:
    """
    Create database entry only when identifier is first used for Plings service.
    
    This is the ONLY time a manufactured identifier enters the database.
    """
    parsed = parse_plings_url(identifier_url)
    
    # Verify cryptographic authenticity
    if not verify_identifier(parsed):
        raise InvalidIdentifierError("Cryptographic verification failed")
    
    # Check if already in database
    existing = get_object_by_identifier(parsed.instance_key)
    if existing:
        return existing  # Already registered
    
    # FIRST TIME: Create database entry
    object_instance = create_object_instance({
        "identifier": parsed.instance_key,
        "path": parsed.path,
        "class_pointer": parsed.class_pointer,  # Class pointer from URL parameter
        "first_scan_at": datetime.now(),
        "scan_location": scan_context.get("gps"),
        "manufacturer": get_manufacturer_from_path(parsed.path)
    })
    
    return object_instance

Scale Comparison: Coca-Cola

Traditional System Database Load

Daily Production: 2,000,000,000 cans
Database Writes: 2,000,000,000 INSERT statements
Database Size Growth: 2B records/day × 365 days = 730B records/year
Storage: ~50 TB per year (assuming 70 bytes per record)
Infrastructure: Massive real-time database clusters

Plings System Database Load

Daily Production: 2,000,000,000 cans (identifiers generated offline)
Database Writes: ~200,000 INSERT statements (0.01% scan rate)
Database Size Growth: 200K records/day × 365 days = 73M records/year  
Storage: ~5 GB per year (1000x reduction)
Infrastructure: Standard database, no real-time production dependency

Manufacturing Benefits

Production Line Independence

Coca-Cola Atlanta Plant:
- Generates 360,000 identifiers per hour per line (100 per second)
- NO real-time database connection required
- NO network dependency during production
- NO single point of failure from database outages
- Continues production during connectivity issues
- Works in remote facilities without infrastructure
- Parallel generation across multiple production lines

Competitive Advantages Over Traditional Systems

Manufacturing Independence:

  • Traditional: Production stops during network outages
  • Plings: Production continues independently using cryptographic generation

Global Deployment:

  • Traditional: Requires database infrastructure in every region
  • Plings: Works anywhere with just manufacturer’s private key

Batch Operations:

  • Traditional: Sequential database requests (bottleneck at scale)
  • Plings: Parallel cryptographic generation (unlimited scale)

Remote Facilities:

  • Traditional: Requires reliable internet and database connectivity
  • Plings: Operates completely offline in any location

Cost Structure

Traditional Licensing Model:
- Pay per identifier generated
- Cost scales with production volume
- Upfront fees for bulk generation

Plings Model:
- Pay per identifier that uses services
- Cost scales with actual value delivered
- No upfront costs for unused identifiers

Privacy and Data Minimization

Data Protection Compliance

  • GDPR Compliance: Only store data when users actively engage with services
  • Privacy by Design: Bulk manufacturing data never collected
  • Minimal Data Collection: Database contains only meaningful interactions
  • User Control: Consumers choose which objects to register

Technical Privacy Features

# Manufacturing: No personal data collected
coca_cola.generate_identifier(batch="5847", instance=1)  # Local generation only

# Consumer Choice: Data collection only with explicit action  
user.scan_and_claim(identifier)  # Creates database entry
user.ignore_scan(identifier)     # No database entry created

Important Constraint: PlingsIdentifiers can only be attached to individual physical objects - they cannot be attached to Set Objects, which are logical groupings without physical presence.

Storage Architecture: Store Minimal, Derive Maximal

Core Philosophy

Plings identifiers are designed around a fundamental principle: minimize storage requirements while maximizing functionality through derivation and computation.

Traditional vs Plings Storage Model

Traditional Systems:

Manufacturing → Create DB Record → Store All Fields → Ship Product
                    ↓
            Database grows by 100% of production

Plings System:

Manufacturing → Generate Cryptographically → Ship Product
                       ↓
            Database grows by ~0.01% of production
                       ↓
            Fields derived on-demand when needed

Technical Implementation: derive_hd_key Function

def derive_hd_key(wallet_master_key: str, hd_derivation_path: str) -> str:
    """
    Derive a Solana address from HD wallet master key and derivation path.
    
    Args:
        wallet_master_key: Base58-encoded master private key
        hd_derivation_path: BIP32 path (e.g., "m/44'/501'/1'/3'/1255'/2'/5847'/90000'")
    
    Returns:
        Base58-encoded Solana address (Ed25519 public key) - THIS IS THE INSTANCE KEY
    
    Implementation:
        1. Parse HD derivation path into indices
        2. Derive child private key using BIP32 Ed25519
        3. Generate Ed25519 public key from private key
        4. Return Base58-encoded public key (Solana address)
    """
    # Implementation details:
    # - Use BIP32-Ed25519 derivation (not secp256k1)
    # - Follow Solana's SLIP-0044 standard (coin type 501')
    # - The resulting public key IS the Solana address
    # - This address can receive SOL, USDC, and interact with programs

Field Classification Framework

✅ Always Stored (Essential Data):

  • instanceKey: Solana address (Base58-encoded Ed25519 public key) - enables direct payments
  • path: Hierarchical location in manufacturer namespace
  • first_scan_at: When identifier first entered system (if ever)
  • object_relationships: Links to physical objects (when assigned)

🔄 Always Derivable (Never Store):

  • shortCode: instanceKey.substring(0, 4) - human readable reference
  • qrCodeUrl: f"https://s.plings.io?t=q&i={instanceKey}&p={path}" - scannable URL
  • publicKey: Resolved via manufacturer registry lookup
  • verification_result: Computed through cryptographic verification

📦 Temporarily Generated (Batch Operations):

  • CSV exports for printing: Generated on-demand, destroyed after use
  • Batch manifests: Temporary files for production runs
  • QR code images: Generated when needed, not stored long-term

Production Implementation Guidelines

For Manufacturers:

# ✅ CORRECT: Generate for printing, don't store
def create_production_batch(count: int):
    batch_record = create_batch_metadata(count)  # Store minimal batch info
    csv_file = generate_temporary_csv(count)     # Temporary export
    
    # CSV contains: instanceKey, shortCode, qrCodeUrl for printing
    # After printing: delete CSV, keep only batch_record
    
    return csv_file

# ❌ INCORRECT: Don't store derivable fields permanently  
def store_all_identifier_data(identifier):
    db.save({
        "instanceKey": identifier.instanceKey,
        "shortCode": identifier.instanceKey[:4],     # DON'T STORE
        "qrCodeUrl": generate_url(identifier),       # DON'T STORE
        "publicKey": registry.lookup(identifier)     # DON'T STORE
    })

For API Responses:

// ✅ CORRECT: Compute derivable fields in response
function getIdentifierInfo(instanceKey, path) {
    return {
        instanceKey: instanceKey,           // From database
        path: path,                        // From database
        shortCode: instanceKey.slice(0,4), // Derived
        qrCodeUrl: buildUrl(instanceKey, path), // Derived
        publicKey: await resolvePublicKey(path) // Resolved
    }
}

Performance Considerations

When to Cache vs Compute:

Always Compute (Fast Operations):

  • shortCode extraction: O(1) string operation
  • URL construction: Simple string templating
  • Path parsing: Lightweight string operations

Cache When Appropriate:

  • PublicKey registry: Network lookup required
  • Cryptographic verification: CPU-intensive operations
  • QR code image generation: Image processing overhead

Never Store Long-term:

  • Temporary batch files for manufacturing
  • Generated QR code images
  • Computed verification results

Benefits of This Architecture

Operational Benefits:

  • 99% reduction in database storage for manufacturing data
  • No network dependency during production
  • Unlimited parallel generation across production lines
  • Zero single points of failure in manufacturing

Cost Benefits:

  • Storage costs scale with usage, not production
  • No upfront database provisioning for bulk generation
  • Reduced backup and disaster recovery requirements

Technical Benefits:

  • Self-contained identifiers work offline
  • Cryptographic verification without stored keys
  • Consistent data through algorithmic derivation
  • Future-proof design - new fields can be derived from existing data

This architecture enables Plings to scale to global manufacturing volumes while maintaining minimal infrastructure requirements and maximum operational flexibility.

URL Format

Why URLs?

Plings identifiers are presented as URLs rather than raw cryptographic strings for several critical reasons:

Universal Scanning Access:

  • Anyone can scan a Plings code using any device with a camera
  • QR codes, NFC tags, and barcodes all natively support URL encoding
  • Smartphones automatically recognize URLs and offer to open them
  • No special app installation required for basic functionality

Lost & Found System:

  • Every scan is recorded with timestamp and GPS location (when available)
  • If an object is marked as “lost”, the owner receives notifications when scanned
  • If someone finds an object that appears lost, they can initiate contact with the owner
  • Example: A ski glove found on the slopes - the finder can contact the owner to arrange pickup or drop-off

Progressive Enhancement:

  • Works immediately in any web browser
  • Enhanced experience available through dedicated mobile apps
  • Graceful fallback when Plings app is not installed

Human Usability:

  • URLs are familiar and trusted by users
  • Visual confirmation of destination (scan.plings.io domain)
  • Clear indication this is an official Plings identifier

Technical Benefits:

  • Standard HTTP/HTTPS protocols handle authentication, caching, and security
  • URL parameters provide structured data encoding
  • Compatible with existing web infrastructure and analytics

Canonical URL Pattern

All Plings identifiers resolve to this canonical URL pattern:

https://s.plings.io?t=<tag_type>&p=<path>&cp=<class_pointer>&i=<instance_key>&tx=<transaction_type>&svc=<service_id>&price=<amount>&dur=<duration>&provider=<provider_id>

Base Format (shows all available services):

https://s.plings.io?t=q&p=<path>&cp=<class_pointer>&i=<instance_key>

Service-Specific Format (direct service access):

https://s.plings.io?t=q&p=<path>&cp=<class_pointer>&i=<instance_key>&tx=rent&dur=1d&price=25&provider=bikeshare_co

Class Pointer Separate from Path

Key Architecture: Class information is provided as a separate parameter (cp=) rather than embedded in the path, enabling unlimited class space and cryptographic verification while maintaining readable paths.

URL Optimization for Physical Constraints

Shortened Domain: https://s.plings.io (18 characters vs 23)

  • 5-character savings per identifier for physical space constraints
  • Subdomain retained for backend routing flexibility
  • Future backends: Different tag types can use specialized subdomains

Routing Architecture:

  • s.plings.io → Standard scanning backend
  • nfc.plings.io → NFC-specific features (future)
  • ar.plings.io → Augmented reality integration (future)
  • api.plings.io → Direct API access (existing)

Parameters

Parameter Description Required Format
t Tag Type Yes Single character: q=QR, n=NFC, b=Barcode, etc.
i Instance Key Yes Base58-encoded unique instance identifier
p Path Yes Dot-separated HD wallet path (no class information)

Example URLs

# Basic identifier (shows all available services)
https://s.plings.io?t=q&p=2.G.2.2.7P.9j&cp=3K7mX9abDcE&i=A7Kj9mN2pQ8Rt5KdBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s

# Service-specific rental URL (ACME Mountain Bike)
https://s.plings.io?t=q&p=2.G.2.2.7P.9j&cp=3K7mX9abDcE&i=A7Kj9mN2pQ8Rt5KdBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s&tx=rent&dur=1d&price=25&provider=bike_rental_co

# Service-specific repair URL (ACME Mountain Bike)
https://s.plings.io?t=q&p=2.G.2.2.7P.9j&cp=3K7mX9abDcE&i=A7Kj9mN2pQ8Rt5KdBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s&tx=service&svc=repair&price=150&provider=bike_shop_123

# Generic QR code (Plings generic tags)
https://s.plings.io?t=q&p=2.2.2.2.2&cp=4K7mX9abDcE&i=8fJ3kL2mP9qR7sT1uV5wX8yBcD2fG3hJ5kLmNp9rTv8X

# Coca-Cola Classic can with purchase option
https://s.plings.io?t=q&p=2.3j.4.5Q.3L8z&cp=2rt5KqL9mXw&i=7KdN2mPqR8sT4uVwXzC1eDqRpL5mBnJxGaHk9WcE3FZbGi2r&tx=buy&price=150

# Insurance service for Coca-Cola product
https://s.plings.io?t=q&p=2.3j.4.5Q.3L8z&cp=2rt5KqL9mXw&i=7KdN2mPqR8sT4uVwXzC1eDqRpL5mBnJxGaHk9WcE3FZbGi2r&tx=insurance&svc=theft_protection&price=200&dur=1y&provider=insurance_corp

Service Parameter Documentation

Core Parameters

| Parameter | Description | Required | Format | |———–|————-|———-|——–| | t | Tag Type | Yes | Single character: q=QR, n=NFC, b=Barcode | | p | Path | Yes | Dot-separated HD wallet path (no class information) | | c | Class Pointer | Yes | 11-character class pointer (SHA256 hash of manufacturer_pubkey:path_to_pointer) | | i | Solana Address | Yes | 44-character Solana address for payments |

Service Parameters (Optional)

| Parameter | Description | Required | Format | |———–|————-|———-|——–| | tx | Transaction Type | No | buy, rent, service, insurance, subscription | | svc | Service ID | No | Service identifier: daily, repair, theft_protection | | price | Price Amount | No | Price in smallest currency unit (cents, lamports, etc.) | | dur | Duration | No | Duration format: 1d, 1w, 1m, 1y | | provider | Provider ID | No | Service provider identifier |

Transaction Types

buy - Ownership Transfer:

  • Transfers permanent ownership of the object
  • Mints/transfers NFT to buyer
  • Updates object ownership in database
  • Example: tx=buy&price=50000 (purchase for $500.00)

rent - Temporary Usage:

  • Creates rental agreement for specified duration
  • Maintains original ownership
  • Requires security deposit (can be included in price)
  • Example: tx=rent&dur=7d&price=15000 (rent for 7 days at $150.00)

service - Maintenance/Repair:

  • Books service appointment with provider
  • Can be one-time or recurring
  • Supports various service types
  • Example: tx=service&svc=repair&price=7500&provider=bike_shop_123

insurance - Protection Coverage:

  • Purchases insurance coverage for object
  • Usually recurring subscription
  • Covers theft, damage, or specific risks
  • Example: tx=insurance&svc=theft_protection&price=20000&dur=1y

subscription - Recurring Services:

  • Ongoing services like monitoring, maintenance
  • Automatic recurring billing
  • Can include digital services
  • Example: tx=subscription&svc=gps_tracking&price=500&dur=1m

Path Structure Examples

Path Breakdown Product
2.G.2.2.7P.9j + 4K7mX9abDcE ACME(15→G) → Standard(1→2) → Plant1(1→2) → Batch(2024→7P) → Instance(158→9j) + Class Pointer ACME Mountain Bike
2.3j.4.5Q.3L8z CocaCola(3→3j) → Atlanta(1255→3j) → Batch(5847→5Q) → Can(90000→3L8z) Coca-Cola Classic (Class: 2rt5KqL9mXw)
2.3j.3.2.56.5Q.L2.3L8z CocaCola(3→3j) → Delegation(2→3) → Atlanta(1→2) → Line(255→56) → Batch(5847→5Q) → ProductionLine1(L1→L2) → Can(90000→3L8z) Coca-Cola Classic Complex (Class: jj9QmRxPtK8)
2.2.2.2.2 Plings(1→2) → Generic(1→2) → Batch(1→2) → Instance(1→2) Plings Generic Sticker (Class: 4K7mX9abDcE)

Class Pointer System

Migration Note: The legacy dash-marker system has been replaced with cryptographic class pointers. For details, see update-class-system.md.

Class Pointer Examples:

  • Path: 2.3j.4.5Q.3L8z, Class Pointer: 2rt5KqL9mXw (Classic Coke)
  • Path: 2.G.2.2.7P.9j, Class Pointer: 3K7mX9abDcE (Mountain Bike)
  • Path: 2.3.2.7j.Q2.2, Class Pointer: 4K7mX9abDcE (IKEA Table)

Class Verification: Class pointers are generated from SHA256(manufacturer_pubkey:path_to_pointer) using the first 8 bytes, Base58-encoded to 11 characters, and provide cryptographic proof of manufacturer authorization.

Canonical Class Pointer Generation Algorithm

import hashlib
import base58

def generate_class_pointer(manufacturer_pubkey: str, path_to_pointer: str) -> str:
    """
    Canonical class pointer generation algorithm.
    
    CRITICAL: All implementations MUST use this exact algorithm.
    
    Steps:
    1. Create message: "{manufacturer_pubkey}:{path_to_pointer}"
    2. SHA-256 hash the message
    3. Take first 8 bytes (64 bits) of hash
    4. Base58 encode the 8 bytes
    5. Result: 11-character class pointer
    
    Args:
        manufacturer_pubkey: Ed25519 public key in base58 format (44 chars)
        path_to_pointer: Path segments before batch.instance (e.g., "2.3.2")
    
    Returns:
        Class pointer: 11-character base58 string representing 8 bytes
        
    Example:
        generate_class_pointer(
            "5Kd3NBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s6Jh5G3f2A",
            "2.3.2"
        ) → "4K7mX9abDcE"
    """
    # Step 1: Create message with colon separator
    message = f"{manufacturer_pubkey}:{path_to_pointer}"
    
    # Step 2: SHA-256 hash the UTF-8 encoded message
    hash_value = hashlib.sha256(message.encode('utf-8')).digest()
    
    # Step 3: Take exactly first 8 bytes (64 bits)
    raw_bytes = hash_value[:8]
    
    # Step 4: Base58 encode the 8 bytes
    class_pointer = base58.b58encode(raw_bytes).decode('ascii')
    
    # Result: Always 11 characters for 8-byte input
    assert len(class_pointer) == 11, f"Expected 11 chars, got {len(class_pointer)}"
    
    return class_pointer

def verify_class_pointer(manufacturer_pubkey: str, path_to_pointer: str, class_pointer: str) -> bool:
    """Verify class pointer authenticity"""
    expected_pointer = generate_class_pointer(manufacturer_pubkey, path_to_pointer)
    return expected_pointer == class_pointer

def extract_path_to_pointer(full_path: str) -> str:
    """
    Extract path_to_pointer from full path using canonical rule:
    The last TWO segments are ALWAYS batch.instance
    """
    segments = full_path.split('.')
    if len(segments) < 3:
        raise ValueError("Minimum path: wallet.manufacturer.batch.instance")
    return '.'.join(segments[:-2])  # Everything except last 2 segments

Cryptographic Foundation

HD Wallet Structure

Plings uses BIP32 Hierarchical Deterministic (HD) wallets with a single master key for the entire ecosystem. All identifiers must cryptographically derive from this master to be valid:

Blockchain Compatibility

Solana HD Wallet Standard: Plings uses Solana’s Ed25519 HD wallet standard with BIP32 derivation paths for all cryptographic operations.

Solana Integration: Uses standard BIP32 paths (m/44'/501'/wallet'/...) following SLIP-0044 with Ed25519 curve. Standard purpose code 44' with Solana coin type 501' and wallet-first hierarchy. Enables near-instant transactions (~400ms), minimal fees, and optimal mobile payment experience.

Payment Integration Examples:

# Solana payment URL  
https://s.plings.io?t=q&p=2.3j.4.5Q.3L8z&cp=2rt5KqL9mXw&i=<sol_address>&pay=5000

Derivation Structure: BIP32 path structure using Ed25519 curve for all cryptographic operations.

Master Key (Plings) - Single Source of Trust - Solana Standard (SLIP-0044)
├── m/44'/501'/1' → Plings (manufacturer anchor)
│   ├── 1/1 → Generic Tags
│   │   ├── 1/1 → Generic Sticker Tags (class key)
│   │   │   ├── 1/1/1 → InstanceKey 1
│   │   │   ├── 1/1/2 → InstanceKey 2
│   │   │   └── 1/1/n → InstanceKey n
│   │   └── 1/2 → Generic Etched Tags (class key)
│   │       └── 1/2/n → InstanceKey n
│   └── 1/2 → [Future Plings Categories]
├── m/44'/501'/2' → IKEA (manufacturer anchor)
│   ├── 2/1 → Class Definitions (public verification)
│   │   ├── 2/1/1 → Chair classes
│   │   ├── 2/1/2 → Table classes
│   │   └── 2/1/n → More classes
│   └── 2/2' → Producer Delegation (hardened)
│       ├── 2/2'/1' → Producer 1 (furniture specialist)
│       │   ├── 2/2'/1'/2'/3' → Table class, Batch 3
│       │   │   ├── 2/2'/1'/2'/3'/1 → Table instance 1
│       │   │   └── 2/2'/1'/2'/3'/n → Table instance n
│       │   └── 2/2'/1'/1'/5' → Chair class, Batch 5
│       │       └── 2/2'/1'/1'/5'/n → Chair instances
│       └── 2/2'/n' → More producers
└── m/44'/501'/3-999' → [Other Manufacturers]

Critical Security Feature: Every Plings identifier must cryptographically prove it derives from this master key. External generation is impossible without compromising the master key.

Key Types

AnchorKey (Class Key)

  • First non-hardened public key in the derivation path
  • Represents an ObjectClass in the database
  • Used for offline verification of authenticity
  • Can derive unlimited child InstanceKeys

InstanceKey

  • Child key derived from AnchorKey
  • Represents a specific ObjectInstance
  • Encoded as the i parameter in URLs
  • Must be verifiable against its parent AnchorKey

Verification Process

Manufacturer-as-Anchor Verification Model

Two-Level Verification: Plings uses a manufacturer-as-anchor model where each manufacturer’s root key serves as the verification anchor for their class hierarchy.

For Manufacturer-Issued Identifiers (e.g., IKEA)

  1. Extract identifiers from URL parameters c (ClassKey), i (InstanceKey), and p (path)
  2. Parse path to identify manufacturer (e.g., 2.1.2.1 → IKEA manufacturer 2)
  3. Request verification: Send identifiers to Plings API or IKEA’s verification service
  4. Verification authority: Plings or IKEA uses their private keys to verify the cryptographic chain
  5. Return attestation: Verification service confirms authenticity and returns manufacturer details
  6. Result: Trusted authority confirms this is an authentic IKEA product

For Generic Tags (Plings-issued)

  1. Extract identifiers from URL parameters i (InstanceKey) and p (path)
  2. Parse path to determine allocation source (e.g., 1.1.1 → Plings allocation)
  3. Request verification: Send identifiers to Plings verification API
  4. Plings verification: Plings uses master private key to verify the cryptographic derivation
  5. Return attestation: Plings confirms identifier validity and path allocation

Key Advantages:

  • True manufacturer authentication: Verification authorities can confirm IKEA issued the class definition
  • Trusted verification: Clients receive authenticated attestation from Plings or manufacturers
  • Compact QR codes: No certificates needed, just path parameter
  • Cryptographic purity: Full BIP32 verification chain maintained

Path Hierarchy System

The p parameter encodes a right-aligned, dot-separated hierarchy that provides human-readable organization while maintaining cryptographic security.

Path Rules

  • Right-aligned: Most specific identifier is rightmost
  • Dot-separated: Each level separated by . character
  • Manufacturer-defined: Each organization controls their own namespace
  • Case-insensitive: Normalized to lowercase for consistency

Path Examples

Plings Generic Tags: | Path | HD Derivation | Interpretation | |——|—————|—————-| | 1.1.1 | m/44'/501'/1/1/1 | Plings generic sticker tags | | 1.1.2 | m/44'/501'/1/1/2 | Plings generic etched tags | | 1.2.1 | m/44'/501'/1/2/1 | Plings test batch series 1 |

IKEA Producer Delegation Examples:

IKEA Architecture Levels: | Path | HD Derivation | Type | Purpose | |——|—————|——|———| | 2 | m/44'/501'/2' | Manufacturer Anchor | IKEA root (hardened, for verification) | | 2.1 | m/44'/501'/2/1 | Class Definitions | Public class keys (non-hardened) | | 2.2 | m/44'/501'/2/2' | Producer Delegation | Private producer keys (hardened) |

Class Definitions (public, verifiable by anyone): | Path | HD Derivation | Class Type | Purpose | |——|—————|————|———| | 2.1.1 | m/44'/501'/2/1/1 | Chair Class | IKEA chair specifications | | 2.1.2 | m/44'/501'/2/1/2 | Table Class | IKEA table specifications | | 2.1.3 | m/44'/501'/2/1/3 | Lamp Class | IKEA lamp specifications |

Producer Delegation (IKEA delegates to manufacturers): | Path | HD Derivation | Producer | Capability | |——|—————|———-|————| | 2.2.1 | m/44'/501'/2/2'/1' | Producer 1 | Furniture specialist | | 2.2.2 | m/44'/501'/2/2'/2' | Producer 2 | Lighting specialist | | 2.2.3 | m/44'/501'/2/2'/3' | Producer 3 | Multi-category |

Production Batches (producer creates class-specific batches): | Path | HD Derivation | Production Details | |——|—————|——————-| | 2.2.1.2.3 | m/44'/501'/2/2'/1'/2'/3' | Producer 1, Table class, Batch 3 | | 2.2.1.1.5 | m/44'/501'/2/2'/1'/1'/5' | Producer 1, Chair class, Batch 5 | | 2.2.2.3.1 | m/44'/501'/2/2'/2'/3'/1' | Producer 2, Lamp class, Batch 1 |

Instance Keys (individual products from batches): | Path | HD Derivation | Product Identity | |——|—————|——————| | 2.2.1.2.3.1 | m/44'/501'/2/2'/1'/2'/3'/1 | Table #1 from Producer 1, Batch 3 | | 2.2.1.2.3.2 | m/44'/501'/2/2'/1'/2'/3'/2 | Table #2 from Producer 1, Batch 3 | | 2.2.2.3.1.1 | m/44'/501'/2/2'/2'/3'/1'/1 | Lamp #1 from Producer 2, Batch 1 |

Complete URL Examples (instance_key is the Solana address):

  • Generic Plings tag: https://s.plings.io?t=q&p=2.2.2.2&cp=4K7mX9abDcE&i=8TxW4nZBE3yPcBYGqka29FZkUETQzfma8xUG47jKbLRH
  • IKEA table: https://s.plings.io?t=q&p=2.3.2.7j.2&cp=3K7mX9abDcE&i=5KCzqG4PsJgMkJxVLEy8QDzRnTDNR4gYk5y7JhP9ptFq
  • IKEA lamp: https://s.plings.io?t=q&p=2.3.3.2.2.F3.2&cp=2rt5KqL9mXw&i=2njmPfTKN6KcqtQKzKf7fKKGKEoL4p5eUpfmQ5rJSfQD
  • Coca-Cola can: https://s.plings.io?t=q&p=2.3j.4.5Q.3L8z&cp=4K7mX9abDcE&i=9WzDtWMvyF8tZkGJbgFDv6GcPTpbMq8KwZvGNqhJmvFf

Coca-Cola Path Examples: | Path | HD Derivation | Product Identity | |——|—————|——————| | 2.3j.4.5Q.3L8z + 4K7mX9abDcE | m/44'/501'/1'/3'/1255'/2'/5847'/90000' | Classic Coke, Atlanta plant (1255→3j), Batch 5847→5Q, Can #90000→3L8z | | 2.3j.4.5Q.3L8A + 3K7mX9abDcE | m/44'/501'/1'/3'/1255'/1'/5847'/90001' | Diet Coke, Atlanta plant (1255→3j), Batch 5847→5Q, Can #90001→3L8A | | 2.3k.4.5Q.2ZN + 4K7mX9abDcE | m/44'/501'/1'/3'/1256'/2'/5847'/45000' | Classic Coke, Mexico City plant (1256→3k), Batch 5847→5Q, Can #45000→2ZN | | 2.3j.3.2.56.5Q.L2.3L8z + 4K7mX9abDcE | m/44'/501'/1'/3'/2'/1'/255'/2'/5847'/1'/90000' | Classic Coke, Complex hierarchy, Line 1→L2, Can #90000→3L8z | | 2.3v.4.5Q.mP + 2rt5KqL9mXw | m/44'/501'/1'/3'/1300'/3'/5847'/30000' | Sprite, Berlin plant (1300→3v), Batch 5847→5Q, Can #30000→mP |

Product Information Resolution

POS Integration Example:

def extract_product_info(path: str, class_pointer: str) -> dict:
    """Extract manufacturer and resolve class from separate class pointer."""
    parts = path.split('.')
    manufacturer_id = int(parts[0])  # 3 = Coca-Cola
    
    # Class information comes from separate class pointer parameter
    # class_pointer is an 11-character hash like "4K7mX9abDcE"
    
    if not class_pointer or len(class_pointer) != 11:
        raise ValueError("Valid 11-character class pointer required")
    
    return {
        "manufacturer": get_manufacturer_name(manufacturer_id),
        "product_class": get_product_class_from_pointer(class_pointer),
        "sku_mapping": map_to_internal_sku_from_pointer(class_pointer)
    }

# Example usage
path = "2.3j.4.5Q.3L8z"  # Path contains NO class information
class_pointer = "4K7mX9abDcE"  # Class comes from separate parameter
product_info = extract_product_info(path, class_pointer)
# Returns: {"manufacturer": "Coca-Cola", "product_class": "Classic Coke", "sku_mapping": "COKE-355ML-CLASSIC"}

# Works with any hierarchy complexity - class always separate
path_complex = "2.3.2.56.5Q.L2.P16.3L8z"  # Complex path structure
class_pointer_complex = "4K7mX9abDcE"  # Same class, different path
product_info = extract_product_info(path_complex, class_pointer_complex)  
# Returns: {"manufacturer": "Coca-Cola", "product_class": "Classic Coke", ...}

Two-Level Verification System:

Anchor Key Verification Process

Consumer Verification Flow: Path 2.3j.4.5Q.3L8z + Class Pointer 4K7mX9abDcE

  1. Extract anchor path: 2.3j (Coca-Cola Atlanta plant)
  2. Query registry: “What’s the public key for anchor 2.3j?”
  3. Extract class: Use class pointer 4K7mX9abDcE (Classic Coke)
  4. Verify instance: Check instance 90000 derives from anchor key + full path
  5. Result: “Cryptographically proven authentic Coca-Cola Classic from Atlanta plant” ✓

Anchor Key Registry Lookup

What’s stored in registry:

  • Anchor path: 3.1255
  • Public key: Coca-Cola Atlanta plant public key
  • Authorization: Coca-Cola authorized this anchor for production

What’s NOT stored:

  • Individual instances (90000)
  • Batch details (5847)
  • Class definitions (class determined from class pointer)

POS System Integration

Verification + Product Mapping:

def verify_and_map_product(url: str) -> dict:
    parsed = parse_plings_url(url)  # Extract i= and p= parameters
    path_parts = parsed.path.split('.')
    
    # Extract anchor for verification (manufacturer-defined length)
    # For Coca-Cola: might be "3.1255" or "3.2.1.255" depending on hierarchy
    anchor_path = determine_anchor_path(path_parts)  
    anchor_key = get_anchor_public_key(anchor_path)
    
    # Verify cryptographic authenticity
    is_authentic = verify_instance_from_anchor(parsed.instance_key, anchor_key, parsed.path)
    
    # Extract product information using class pointer  
    manufacturer = int(path_parts[0])  # 2 = Coca-Cola (base58 decoded from 3)
    product_class = get_product_class_from_pointer(parsed.class_pointer)  # Use class pointer
    
    return {
        "authentic": is_authentic,
        "manufacturer": "Coca-Cola",
        "product": "Classic Coke 355ml",
        "price": get_pricing(manufacturer, product_class),
        "sku": "COKE-355-CLASSIC"
    }

Manufacturer-as-Anchor Architecture

In Plings HD wallet structure, manufacturers serve as anchor points for all their product classes. This enables true cryptographic verification of manufacturer authenticity.

Anchor Point Rules:

  • Manufacturer level contains the manufacturer’s private verification key (e.g., m/44'/501'/2' for IKEA)
  • All derivation levels may use hardened derivation based on manufacturer security preferences
  • Class keys are derived from manufacturer anchor and verified by the manufacturer or Plings
  • Instance keys derive from class keys through controlled derivation processes

IKEA Example:

Hardened:     m/44'/501'/2' → IKEA Manufacturer Anchor (private key held by IKEA)
Public:       m/44'/501'/2' → IKEA Public Key (published for verification)
Non-hardened: 2/1/2 → IKEA Table Class (derivable from IKEA anchor)
Non-hardened: 2/1/2/1 → Specific table instance (derivable from class)

Verification Benefits:

  • True manufacturer authentication: Verify any IKEA class against IKEA’s known public key
  • Cryptographic proof: No trust required - pure mathematical verification
  • Offline capability: All verification possible with cached manufacturer keys
  • POS system compatibility: Point-of-sale can verify pricing authority cryptographically

Client Implementation Guide

This section provides comprehensive step-by-step instructions for implementing Plings identifier verification in client applications.

Prerequisites

Required Libraries:

  • BIP32 implementation: For HD key derivation with Ed25519 support (e.g., @solana/web3.js, ed25519-hd-key)
  • Base58 decoder: For identifier parsing (e.g., bs58, base-x)
  • Cryptographic library: For Ed25519 key operations (e.g., tweetnacl, @noble/ed25519)

Required Data:

  • Manufacturer registry: Cached mapping of manufacturer IDs to public keys
  • Plings master public key: For generic tag verification

Complete Verification Algorithm

1. Parse Plings URL

function parsePlingsUrl(url) {
    const urlParams = new URLSearchParams(url.split('?')[1]);
    
    return {
        tagType: urlParams.get('t'),        // 'q', 'n', 'b'
        instanceKey: urlParams.get('i'),    // Base58 instance key (required)
        path: urlParams.get('p')            // Dot-separated path with embedded class (required)
    };
}

// Example
const parsed = parsePlingsUrl('https://s.plings.io?t=q&i=5Kd3NBUo...&p=3.2.1.255.2.5847.90000');
// Returns: { tagType: 'q', instanceKey: '5Kd3NBUo...', path: '3.2.1.255.2.5847.90000' }

2. Determine Verification Type

function getVerificationType(parsed) {
    const pathParts = parsed.path.split('.');
    const manufacturerId = parseInt(pathParts[0]);
    
    if (manufacturerId === 1) {
        return {
            type: 'PLINGS_GENERIC',
            manufacturer: 'Plings',
            anchorPath: 'm/44\'/501\'/1\''
        };
    } else {
        return {
            type: 'MANUFACTURER_ISSUED',
            manufacturerId: manufacturerId,
            anchorPath: `m/44'/501'/${manufacturerId}'`
        };
    }
}

3. Anchor Key Verification (e.g., Coca-Cola)

async function verifyManufacturerIdentifier(parsed, anchorRegistry) {
    const pathParts = parsed.path.split('.');
    const manufacturerId = parseInt(pathParts[0]);
    
    // Step 1: Extract anchor path (typically first 4 parts)
    const anchorPath = pathParts.slice(0, 4).join('.');  // e.g., "3.2.1.255"
    
    // Step 2: Get anchor public key from registry
    const anchorPubKey = await anchorRegistry.getPublicKey(anchorPath);
    if (!anchorPubKey) {
        throw new Error(`Unknown anchor: ${anchorPath}`);
    }
    
    // Step 3: Class information comes from separate class_pointer parameter
    // Path contains no class information - only manufacturer hierarchy
    
    // Step 4: Verify instance derives from anchor + full path
    const instanceVerification = await verifyInstanceFromAnchor(
        parsed.instanceKey,
        anchorPubKey,
        parsed.path
    );
    
    return {
        valid: instanceVerification.valid,
        manufacturer: getManufacturerName(manufacturerId),
        productClass: getProductClassFromPointer(parsed.class_pointer),
        anchorPath: anchorPath,
        batchInfo: extractBatchInfo(pathParts),
        reason: instanceVerification.reason
    };
}

async function verifyInstanceFromAnchor(instanceKey, anchorPubKey, fullPath) {
    // Verify instance key derives from anchor key + full path
    // Example: anchor "3.2.1.255" + full path "3.2.1.255.2.5847.90000"
    
    const pathParts = fullPath.split('.');
    const relativePath = pathParts.slice(4).join('/'); // "2/5847/90000"
    
    try {
        // Derive expected instance key from anchor
        const hdKey = HDKey.fromPublicKey(anchorPubKey);
        const derivedKey = hdKey.derive(relativePath);
        
        // Compare with provided instance key
        const providedKeyBuffer = base58.decode(instanceKey);
        const expectedKeyBuffer = derivedKey.publicKey;
        
        return {
            valid: providedKeyBuffer.equals(expectedKeyBuffer),
            derivationPath: `anchor/${relativePath}`,
            verificationMethod: 'anchor_key_derivation'
        };
    } catch (error) {
        return {
            valid: false,
            reason: `Anchor derivation failed: ${error.message}`
        };
    }
}

async function verifyInstanceKey(instanceKey, classKey, pathParts) {
    // For manufacturer issued: verify instance derives from class
    // Example: 2.2.1.2.3.1 → verify instance .1 derives from batch 2.2.1.2.3
    
    if (pathParts.length >= 6) {
        // Producer delegation path: 2.2.1.2.3.1
        const batchPath = pathParts.slice(0, 5).join('/'); // 2/2/1/2/3
        const instanceIndex = pathParts[5]; // 1
        
        // This requires Plings verification for full producer delegation
        return await verifyProducerDelegation(instanceKey, pathParts);
    } else {
        // Direct class instance: derive instance from class key
        const instanceIndex = pathParts[pathParts.length - 1];
        return verifyDirectClassInstance(instanceKey, classKey, instanceIndex);
    }
}

4. Generic Tag Verification (Plings-issued)

async function verifyGenericIdentifier(parsed, plingsMasterPubKey) {
    const pathParts = parsed.path.split('.');
    
    // Verify against Plings master key
    // Example: 1.1.1 → m/44'/501'/1/1/1
    const derivationPath = `m/44'/501'/${pathParts.join('/')}`;
    
    try {
        const hdKey = HDKey.fromPublicKey(plingsMasterPubKey);
        const derivedKey = hdKey.derive(pathParts.join('/'));
        
        const providedKeyBuffer = base58.decode(parsed.instanceKey);
        const expectedKeyBuffer = derivedKey.publicKey;
        
        return {
            valid: providedKeyBuffer.equals(expectedKeyBuffer),
            manufacturer: 'Plings',
            productClass: 'Generic',
            batchInfo: extractGenericBatchInfo(pathParts)
        };
    } catch (error) {
        return {
            valid: false,
            reason: `Derivation failed: ${error.message}`
        };
    }
}

5. Producer Delegation Verification

async function verifyProducerDelegation(instanceKey, pathParts) {
    // Producer delegation requires Plings API for full verification
    // Client can only verify partial batch information
    
    if (pathParts.length < 6) {
        return { valid: false, reason: 'Invalid producer delegation path' };
    }
    
    const [manufacturerId, delegationType, producerId, classId, batchId, instanceId] = pathParts;
    
    // Partial verification: check if instance could derive from batch
    // Note: This is limited verification - full verification requires Plings API
    return {
        valid: true, // Assume valid for partial verification
        requiresServerVerification: true,
        manufacturer: getManufacturerName(manufacturerId),
        producer: `Producer ${producerId}`,
        productClass: getClassNameFromId(classId),
        batch: batchId,
        instance: instanceId,
        warning: 'Partial verification only - contact Plings for full authentication'
    };
}

6. Error Handling and Edge Cases

function handleVerificationErrors(error, parsed) {
    const commonErrors = {
        'INVALID_URL': 'Malformed Plings URL',
        'MISSING_PARAMETERS': 'Required URL parameters missing',
        'INVALID_PATH': 'Invalid path format',
        'UNKNOWN_MANUFACTURER': 'Manufacturer not in registry',
        'KEY_DERIVATION_FAILED': 'BIP32 key derivation failed',
        'NETWORK_ERROR': 'Unable to fetch manufacturer registry'
    };
    
    return {
        valid: false,
        error: error.code || 'UNKNOWN_ERROR',
        message: commonErrors[error.code] || error.message,
        troubleshooting: generateTroubleshootingSteps(error, parsed)
    };
}

function generateTroubleshootingSteps(error, parsed) {
    const steps = [];
    
    if (error.code === 'UNKNOWN_MANUFACTURER') {
        steps.push('Update manufacturer registry from Plings API');
        steps.push('Verify internet connection');
    }
    
    if (error.code === 'KEY_DERIVATION_FAILED') {
        steps.push('Check BIP32 library implementation');
        steps.push('Validate input key format');
    }
    
    return steps;
}

Integration Examples

Hardware Wallet Integration

// Example using Solana hardware wallet support
async function verifyWithHardwareWallet(parsed, wallet) {
    const pathParts = parsed.path.split('.');
    const manufacturerId = pathParts[0];
    
    // Get manufacturer's public key from hardware wallet (Ed25519)
    const derivationPath = `m/44'/501'/${manufacturerId}'`;
    const manufacturerKey = await wallet.getPublicKey(derivationPath);
    
    return await verifyManufacturerIdentifier(parsed, {
        [manufacturerId]: manufacturerKey.publicKey
    });
}

Mobile App Integration

// React Native example
import { HDKey } from 'react-native-hdkey';
import AsyncStorage from '@react-native-async-storage/async-storage';

class PlingsVerifier {
    constructor() {
        this.manufacturerRegistry = {};
        this.loadManufacturerRegistry();
    }
    
    async loadManufacturerRegistry() {
        try {
            const cached = await AsyncStorage.getItem('manufacturer_registry');
            if (cached) {
                this.manufacturerRegistry = JSON.parse(cached);
            }
            
            // Refresh from API
            const response = await fetch('https://api.plings.io/v1/manufacturers');
            const registry = await response.json();
            
            this.manufacturerRegistry = registry;
            await AsyncStorage.setItem('manufacturer_registry', JSON.stringify(registry));
        } catch (error) {
            console.error('Failed to load manufacturer registry:', error);
        }
    }
    
    async verifyQRCode(qrUrl) {
        const parsed = parsePlingsUrl(qrUrl);
        const verificationType = getVerificationType(parsed);
        
        if (verificationType.type === 'MANUFACTURER_ISSUED') {
            return await verifyManufacturerIdentifier(parsed, this.manufacturerRegistry);
        } else {
            const plingsMasterKey = this.manufacturerRegistry['1']; // Plings = manufacturer 1
            return await verifyGenericIdentifier(parsed, plingsMasterKey);
        }
    }
}

Performance Optimization

Caching Strategies

class OptimizedVerifier {
    constructor() {
        this.keyCache = new Map();
        this.verificationCache = new Map();
    }
    
    async verifyWithCaching(parsed) {
        // Check verification cache first
        const cacheKey = `${parsed.classKey}-${parsed.instanceKey}-${parsed.path}`;
        if (this.verificationCache.has(cacheKey)) {
            return this.verificationCache.get(cacheKey);
        }
        
        // Perform verification
        const result = await this.verify(parsed);
        
        // Cache result for 5 minutes
        this.verificationCache.set(cacheKey, result);
        setTimeout(() => this.verificationCache.delete(cacheKey), 5 * 60 * 1000);
        
        return result;
    }
    
    async getDerivedKey(basePath, derivationPath) {
        const fullPath = `${basePath}/${derivationPath}`;
        if (this.keyCache.has(fullPath)) {
            return this.keyCache.get(fullPath);
        }
        
        const derivedKey = await this.performKeyDerivation(basePath, derivationPath);
        this.keyCache.set(fullPath, derivedKey);
        
        return derivedKey;
    }
}

Tag Types: General vs Producer-Issued

Plings supports two distinct types of identifiers, each serving different use cases and providing different levels of information about the tagged object.

General Tags

Purpose: Universal tagging system for any object without manufacturer involvement

Use Case: Personal items, DIY projects, generic objects, prototyping

Object Classification:

  • Progressive classification system - begins with broad categories and evolves to specific classifications
  • Initial classification starts with user input (“This is a lamp”) but becomes increasingly refined
  • Objects migrate from general classes (e.g., “Lamp”) to specific ones (e.g., “IKEA FOTO Table Lamp, White, 25cm”)
  • Class system evolution: The taxonomy becomes more sophisticated over time through machine learning and user contributions
  • Value through specificity: Detailed classifications enable replacement part matching, compatibility checking, and precise object management

Characteristics:

  • Always include path for cryptographic verification
  • Path indicates derivation route within Plings HD wallet
  • Batch-based generation prevents identifier collisions
  • Can be verified offline against Plings master public key
  • Object classification begins with user input and evolves through progressive refinement

URL Format:

https://scan.plings.io/s?t=q&i=<instance_key>&p=<path>

Batch Management:

  • Each production batch gets unique path allocation
  • Example: Path 1.1.1 for generic sticker batch 1, 1.1.2 for batch 2
  • Registry tracks: path, batch size, creation date, purpose
  • Prevents collision and enables audit trails

Example Scenarios:

  • Personal belongings in a household
  • DIY storage organization
  • Generic items without manufacturer branding
  • Quick prototyping and testing

Producer-Issued Tags

Purpose: Manufacturer-authenticated identifiers that include specific product information

Use Case: Commercial products, branded items, supply chain tracking, anti-counterfeiting

Object Classification:

  • Includes specific product class information - the tag tells you exactly what this object is
  • Automatically classified based on manufacturer’s ObjectClass definition
  • Examples: “IKEA JANSJÖ Lamp”, “ACME Mountain Bike Model X”, “Phillips #2 Screwdriver”

Characteristics:

  • Both Class Key (c) and Instance Key (i) provided
  • Offline verification possible against manufacturer’s public key
  • Full cryptographic proof chain
  • Manufacturer accountability and authenticity
  • Path hierarchy support (e.g., ikea.lighting.desk.jansjo)
  • Object type predefined by manufacturer

URL Format:

https://scan.plings.io/s?t=q&cp=<class_key>&i=<instance_key>&p=<path>

Example Scenarios:

  • IKEA furniture with built-in tags
  • Electronics with embedded NFC tags
  • Automotive parts with QR codes
  • High-value items requiring authenticity verification

Key Differences Summary

Aspect General Tags Producer-Issued Tags
Object Information User defines what the object is Manufacturer predefines the exact product
Classification Generic categories (“Lamp”) Specific products (“IKEA JANSJÖ Lamp”)
Security Cryptographic verification via path Full cryptographic authentication with class key
Offline Capability Can verify against Plings master key Can verify complete manufacturer chain
Path Requirement Always includes path (batch tracking) Includes path and class key
Use Case Personal/generic items Commercial/branded products
Setup Tag first, classify later Pre-classified by manufacturer

Multiple Tags Per Object Support

Architecture: The Plings system natively supports multiple identifiers per object through the Neo4j graph relationship model.

Data Model

  • One-to-Many Relationship: Multiple PlingsIdentifier nodes can have IDENTIFIES relationships pointing to the same ObjectInstance
  • No Database Constraints: No unique constraints prevent multiple tag assignments
  • Independent Lifecycle: Each identifier maintains its own status, type, and metadata

Legitimate Use Cases

1. Anti-Counterfeiting (Primary Use Case)

  • Primary Tag: Public QR code for general access
  • Verification Tag: Hidden OTP code under tamper seal
  • Purpose: Combat counterfeiting while maintaining usability

2. Backup and Redundancy

  • Primary Tag: Main identification method (QR code)
  • Backup Tag: Secondary identifier (NFC tag) in case primary is damaged
  • Purpose: Ensure continuous object accessibility

3. Access Level Differentiation

  • Public Tag: Basic object information for anyone
  • Owner Tag: Full object management for authorized users
  • Service Tag: Maintenance and repair access for technicians

4. Physical Placement Strategies

  • External Tag: Visible identifier on object surface
  • Internal Tag: Protected identifier inside object housing
  • Purpose: Balance accessibility with protection from damage

5. Technology Migration

  • Legacy Tag: Existing barcode or older QR format
  • Modern Tag: New NFC or enhanced QR with HD wallet authentication
  • Purpose: Gradual technology upgrades without losing object history

Implementation Examples

Multiple Tag Assignment

// Object with primary QR and backup NFC
(:PlingsIdentifier {instanceKey: "QR_abc123", identifier_type: "QR"})-[:IDENTIFIES]->(:ObjectInstance {id: "obj-456"})
(:PlingsIdentifier {instanceKey: "NFC_def456", identifier_type: "NFC"})-[:IDENTIFIES]->(:ObjectInstance {id: "obj-456"})

Tag Type Querying

// Find all identifiers for an object
MATCH (tag:PlingsIdentifier)-[:IDENTIFIES]->(obj:ObjectInstance {id: $objectId})
RETURN tag.instanceKey, tag.identifier_type, tag.status
ORDER BY tag.identifier_type

Business Rules

  • No Limit: Objects can have unlimited number of identifiers
  • Type Variety: Mix different identifier types (QR, NFC, Barcode)
  • Status Independence: Each tag can be active, disabled, or compromised independently
  • Ownership Clarity: All tags identifying the same object share ownership context

UI/UX Considerations

  • Tag Selection: Users may choose which tag to scan based on convenience
  • Management Interface: Object details should display all associated identifiers
  • Conflict Resolution: Clear indication when multiple tags point to same object

Anti-Counterfeiting Features

Dual Identifier Strategy

For high-value products, manufacturers can deploy multiple identifiers:

  1. Public QR Code - Visible, general information access
    • is_one_time_use: false
    • Standard verification process
    • Can be safely copied/photographed
  2. Hidden OTP Code - Concealed, authenticity verification
    • is_one_time_use: true
    • Under scratch-off layer or tamper seal
    • Single-use verification token

Verification Workflow

graph TD A[Scan Public QR] --> B{Authenticity Concern?} B -->|No| C[Show Product Info] B -->|Yes| D[Prompt for OTP Code] D --> E[Reveal Hidden QR] E --> F[Scan OTP QR] F --> G{Valid OTP?} G -->|Yes| H[Mark Authentic + Burn OTP] G -->|No| I[Flag as Counterfeit]

Counterfeit Detection

Scenario: Multiple items claiming same identity

  1. System detects conflicting location reports for same InstanceKey
  2. Triggers verification challenge to all claiming parties
  3. OTP verification proves authentic owner
  4. Flags fraudulent identifiers as “compromised”
  5. Issues replacement identifiers to legitimate owner

Payment Integration: The Plings Revolution

Direct Object Payments vs Traditional Systems

Traditional GS1/Barcode Flow:

Product → Scanner → POS Terminal → Payment Terminal → Receipt → Exit

Plings Direct Payment Flow:

Scan Object → Pay Object → Own Object (instant NFT transfer)

How Direct Object Commerce Works

When scanning a Plings identifier, the object itself becomes the point of sale:

  1. Object presents payment options via service parameters in URL
  2. Customer chooses transaction type (buy, rent, service, insurance)
  3. Payment goes to object’s Program Derived Address (Solana blockchain)
  4. Smart contract executes appropriate action (ownership transfer, rental agreement, etc.)
  5. NFT minted/transferred as proof of transaction
  6. Object database updated with new status

Service-Enhanced URLs

# Basic identifier (shows all services)
https://s.plings.io?t=q&p=2.G.2.2.7P.9j&cp=3K7mX9abDcE&i=<instance>

# Direct purchase URL
https://s.plings.io?t=q&p=2.G.2.2.7P.9j&cp=3K7mX9abDcE&i=<instance>&tx=buy&price=50000

# Rental service URL
https://s.plings.io?t=q&p=2.G.2.2.7P.9j&cp=3K7mX9abDcE&i=<instance>&tx=rent&dur=1d&price=2500&provider=bikeshare

# Repair service URL  
https://s.plings.io?t=q&p=2.G.2.2.7P.9j&cp=3K7mX9abDcE&i=<instance>&tx=service&svc=repair&price=7500&provider=bike_shop

# Insurance URL
https://s.plings.io?t=q&p=2.G.2.2.7P.9j&cp=3K7mX9abDcE&i=<instance>&tx=insurance&svc=theft&price=20000&dur=1y&provider=insurance_co

Real-World Impact Examples

Vending Machine Revolution:

  • Eliminates coin mechanisms, bill acceptors, card readers
  • Any product becomes “vending-capable” with just a QR code
  • 400ms transaction speed on Solana enables instant dispensing

Retail Store Transformation:

  • No checkout counters, no cashiers, no POS systems
  • Customers scan items and pay directly
  • Store becomes a showroom with self-selling products
  • 70-90% reduction in staffing requirements

Corporate Asset Sales:

  • $50M business jet sold via QR code scan
  • Instant ownership transfer with legal NFT certificate
  • Automated regulatory filings via smart contracts
  • No escrow, no paperwork delays

Solana Blockchain Advantages

Why Solana Enables This Revolution:

  • Transaction Speed: 400ms (vs 10+ minutes for Bitcoin)
  • Transaction Cost: $0.00025 (works for $1 candy or $50M jets)
  • Scalability: 50,000+ TPS current, 600,000 TPS with Firedancer
  • NFT Infrastructure: Native Metaplex standard for ownership
  • Program Derived Addresses: Prevents payment interception

Payment Security Architecture

Problem Solved: Manufacturers can’t intercept payments Solution: Program Derived Addresses (PDAs)

// Payment goes to PDA, not manufacturer's wallet
let payment_address = Pubkey::find_program_address(&[
    b"object_payment",
    "15.2.3.batch.instance".as_bytes()  // HD path as seed
], &plings_program_id);  // Only Plings program controls funds

This ensures:

  • Manufacturers can’t drain payments before object creation
  • Automatic routing to correct recipients
  • Atomic payment + ownership transfer
  • No trust required between parties

Human-Readable Short Codes

4-Character Short Identifier

For physical world usability, each Plings identifier includes a 4-character short code derived from the first 4 characters of the Base58-encoded Solana address (instance key). This allows users to quickly locate physical tags without scanning.

Purpose: Enable optimal human identification of physical tags in real-world scenarios

  • Example: “Look for the plastic bag with 3EWv on it”
  • Derived from: First 4 characters of the Base58-encoded Solana address
  • Character set: Base58 (mixed case: A-Z, a-z, 1-9)
  • Collision resistance: 58⁴ = 11,316,496 combinations (excellent for practical use)
  • Mixed case advantage: Superior visual pattern recognition for human eyes
  • Random distribution: Short codes inherit cryptographic randomness from HD wallet derivation

Implementation:

// Extract short code from instance key
function getShortCode(instanceKey) {
    return instanceKey.substring(0, 4);
}

// Example
const instanceKey = "CuUY3NBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s6Jh5G3f2A";
const shortCode = getShortCode(instanceKey); // Returns "CuUY"

Display Guidelines:

  • Print short code prominently on physical tags alongside QR codes
  • Use consistent typography (monospace font recommended)
  • Consider contrast and readability for various lighting conditions
  • Include short code in mobile app interfaces for tag identification

Use Cases:

  • Warehouse workers locating specific items: “Find bag CuUY”
  • Inventory management: “Get objects tagged with CuUY and KSCy”
  • Customer support: “What are the 4 letters on your tag?”
  • Batch operations: “Process tags CuUY, M3x2, and 7Kpq”

Physical Identifier Implementation

This section provides detailed technical implementation guidance for each physical identifier type supported by Plings. For high-level comparison and selection guidance, see Physical Identifier Types.

QR Code Implementation

Technical Requirements

Minimum QR Code Specifications:
- Version: 1-3 (21x21 to 29x29 modules)  
- Error Correction: Level M (15% recovery) recommended
- Data Mode: Alphanumeric (optimal for URLs)
- Character Set: ISO/IEC 8859-1 (Latin-1)
- Print Quality: 300 DPI minimum, 600 DPI preferred

URL Optimization for Small QR Codes

def optimize_url_for_qr(path: str, instance_key: str) -> str:
    """
    Generate QR-optimized URL with minimal character count
    
    Critical: Every character reduction allows smaller QR codes
    Target: <70 characters for Version 1 QR (21x21 modules)
    """
    # Use short domain and compressed parameters
    base_url = "https://s.plings.io"
    
    # Compress parameters to single characters
    params = {
        "t": "q",           # Type: QR code
        "i": instance_key,  # Instance identifier
        "p": path          # Path (no zero-padding)
    }
    
    # Generate minimal URL
    url = f"{base_url}?t={params['t']}&i={params['i']}&p={params['p']}"
    
    return url

# Example output:
# "https://s.plings.io?t=q&p=2.2.2.2.2&cp=4K7mX9abDcE&i=5Kd3NBUoD3fCRJcyPbT"
# Length: 63 characters (optimal for small QR codes)

QR Code Generation Implementation

import qrcode
from qrcode.image.styledpil import StyledPilImage

def generate_plings_qr(url: str, size_mm: int = 15) -> bytes:
    """
    Generate Plings-branded QR code with optimal settings
    
    Args:
        url: Plings identifier URL
        size_mm: Target QR code size in millimeters
    
    Returns:
        PNG image bytes
    """
    # Calculate optimal version based on URL length
    version = 1 if len(url) <= 65 else 2
    
    qr = qrcode.QRCode(
        version=version,
        error_correction=qrcode.constants.ERROR_CORRECT_M,  # 15% recovery
        box_size=10,  # Pixels per module
        border=4,     # Minimum border width
    )
    
    qr.add_data(url)
    qr.make(fit=True)
    
    # Generate high-quality image
    img = qr.make_image(
        fill_color="black",
        back_color="white",
        image_factory=StyledPilImage
    )
    
    # Convert to bytes
    from io import BytesIO
    buffer = BytesIO()
    img.save(buffer, format='PNG', dpi=(300, 300))
    return buffer.getvalue()

Size Guidelines by Object Type

QR_SIZE_GUIDELINES = {
    "electronics": {
        "min_size_mm": 8,
        "recommended_mm": 12,
        "module_size_mm": 0.4,
        "notes": "Readable on phone screens at 30cm"
    },
    "jewelry": {
        "min_size_mm": 6,
        "recommended_mm": 8,
        "module_size_mm": 0.3,
        "notes": "Micro-QR, laser etching preferred"
    },
    "appliances": {
        "min_size_mm": 15,
        "recommended_mm": 25,
        "module_size_mm": 1.0,
        "notes": "Easy scanning from arm's length"
    },
    "vehicles": {
        "min_size_mm": 20,
        "recommended_mm": 40,
        "module_size_mm": 1.5,
        "notes": "Outdoor durability, high visibility"
    }
}

NFC Tag Implementation

Tag Selection and Programming

import ndef

def program_nfc_tag(path: str, instance_key: str, tag_type: str = "NTAG213") -> dict:
    """
    Program NFC tag with Plings identifier and offline data
    
    Supports: NTAG213 (180 bytes), NTAG215 (540 bytes), NTAG216 (960 bytes)
    """
    # Primary URL record
    primary_url = f"https://s.plings.io?t=n&i={instance_key}&p={path}"
    url_record = ndef.UriRecord(primary_url)
    
    # Offline data record (JSON)
    offline_data = {
        "manufacturer": extract_manufacturer_from_path(path),
        "class": extract_class_from_path(path),
        "batch": extract_batch_from_path(path),
        "verification": compute_verification_hash(path, instance_key),
        "backup_urls": [
            f"https://backup1.plings.io/scan/{instance_key}",
            f"https://backup2.plings.io/scan/{instance_key}"
        ]
    }
    
    # Create NDEF message
    text_record = ndef.TextRecord(json.dumps(offline_data))
    message = ndef.Message([url_record, text_record])
    
    return {
        "ndef_message": message,
        "byte_size": len(message.data),
        "tag_capacity": TAG_CAPACITIES[tag_type],
        "utilization": len(message.data) / TAG_CAPACITIES[tag_type] * 100
    }

TAG_CAPACITIES = {
    "NTAG213": 180,  # bytes
    "NTAG215": 540,  # bytes  
    "NTAG216": 960   # bytes
}

NFC Reading Implementation

// Web NFC API implementation
class PlingsNFCReader {
    async readPlingsTag() {
        if (!('NDEFReader' in window)) {
            throw new Error('NFC not supported on this device');
        }
        
        const ndef = new NDEFReader();
        
        try {
            await ndef.scan();
            
            ndef.addEventListener("reading", ({ message }) => {
                for (const record of message.records) {
                    if (record.recordType === "url") {
                        // Primary Plings URL
                        const url = new TextDecoder().decode(record.data);
                        this.handlePlingsURL(url);
                    } else if (record.recordType === "text") {
                        // Offline verification data
                        const offlineData = JSON.parse(
                            new TextDecoder().decode(record.data)
                        );
                        this.handleOfflineData(offlineData);
                    }
                }
            });
            
        } catch (error) {
            console.error("NFC scan failed:", error);
            throw error;
        }
    }
    
    async writeVerificationData(path, instanceKey) {
        const ndef = new NDEFReader();
        
        const message = {
            records: [
                {
                    recordType: "url",
                    data: `https://s.plings.io?t=n&i=${instanceKey}&p=${path}`
                },
                {
                    recordType: "text",
                    data: JSON.stringify({
                        verification_hash: this.computeHash(path, instanceKey),
                        offline_timestamp: Date.now(),
                        backup_verification: true
                    })
                }
            ]
        };
        
        await ndef.write(message);
    }
}

RFID Tag Implementation

EPC Structure for Plings Paths

def encode_path_to_epc(path: str, instance_key: str) -> str:
    """
    Encode Plings path into EPC-96 format for RFID tags
    
    EPC-96 Structure:
    - Header (8 bits): 0x30 (SGTIN-96)
    - Filter (3 bits): 001 (point of sale)
    - Partition (3 bits): Manufacturer.Category encoding
    - Company Prefix (20-40 bits): Manufacturer ID
    - Item Reference (4-24 bits): Category.Class.Batch
    - Serial Number (38 bits): Instance ID
    """
    parts = path.split('.')
    manufacturer = int(parts[0])
    category = int(parts[1]) 
    class_id = int(parts[2][1:])  # Remove 'C' prefix
    batch = int(parts[3])
    instance = int(parts[4])
    
    # EPC-96 encoding
    header = 0x30  # SGTIN-96
    filter_value = 0x1  # Point of sale
    partition = 0x5  # 40-bit company, 4-bit item
    
    # Encode components
    company_prefix = manufacturer << 8 | category  # 40 bits
    item_reference = class_id << 16 | (batch & 0xFFFF)  # 24 bits
    serial_number = instance  # 38 bits
    
    # Combine into 96-bit EPC
    epc = (header << 88) | (filter_value << 85) | (partition << 82) | \
          (company_prefix << 42) | (item_reference << 18) | serial_number
    
    return hex(epc)[2:].upper().zfill(24)  # 24-character hex string

RFID Reader Integration

import uhfreader

class PlingsRFIDReader:
    def __init__(self, reader_ip: str):
        self.reader = uhfreader.UHFReader(reader_ip)
        
    def read_plings_tags(self, power_level: int = 30) -> List[dict]:
        """
        Read multiple Plings RFID tags simultaneously
        
        Returns list of decoded Plings identifiers
        """
        # Configure reader
        self.reader.set_power(power_level)
        self.reader.set_session(1)  # Session 1 for inventory
        
        # Read tags
        tag_list = self.reader.inventory()
        plings_tags = []
        
        for tag in tag_list:
            epc = tag['epc']
            
            # Check if this is a Plings tag (header = 0x30)
            if epc.startswith('30'):
                decoded = self.decode_plings_epc(epc)
                if decoded:
                    plings_tags.append(decoded)
                    
        return plings_tags
    
    def decode_plings_epc(self, epc_hex: str) -> dict:
        """Decode EPC back to Plings path format"""
        epc_int = int(epc_hex, 16)
        
        # Extract fields
        header = (epc_int >> 88) & 0xFF
        filter_value = (epc_int >> 85) & 0x7
        partition = (epc_int >> 82) & 0x7
        company_prefix = (epc_int >> 42) & 0xFFFFFFFFFF
        item_reference = (epc_int >> 18) & 0xFFFFFF
        serial_number = epc_int & 0x3FFFFFFFFF
        
        # Decode to path components
        manufacturer = company_prefix >> 8
        category = company_prefix & 0xFF
        class_id = item_reference >> 16
        batch = item_reference & 0xFFFF
        instance = serial_number
        
        path = f"{manufacturer}.{category}.C{class_id}.{batch}.{instance}"
        
        return {
            "path": path,
            "epc": epc_hex,
            "read_strength": "varies",
            "tag_type": "RFID_UHF"
        }

Shortcode Implementation

Generation and Validation

class PlingsShortcode:
    # Base58 character set (Bitcoin alphabet) - mixed case for better human readability
    CHARSET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    
    @classmethod
    def generate(cls, instance_key: str) -> str:
        """
        Generate 4-character shortcode from Solana address (instance key)
        
        Uses first 4 characters of Base58-encoded Solana address for:
        - Better collision resistance (58^4 = 11.3M combinations vs 32^4 = 1M)
        - Improved human readability through mixed case characters
        - Direct derivation from cryptographic material (no hash required)
        - Faster visual pattern recognition for humans
        
        Args:
            instance_key: Base58-encoded Solana address (Ed25519 public key)
            
        Returns:
            4-character shortcode with mixed case Base58 characters
        """
        # Use first 4 characters of Base58 Solana address
        return instance_key[:4]
    
    @classmethod
    def validate_format(cls, shortcode: str) -> bool:
        """Validate shortcode format - preserves case sensitivity"""
        if len(shortcode) != 4:
            return False
            
        return all(c in cls.CHARSET for c in shortcode)
    
    @classmethod
    def resolve_to_url(cls, shortcode: str) -> str:
        """
        Generate URL for shortcode entry
        
        Note: Resolution requires database lookup to find matching
        instance key that starts with this shortcode
        """
        return f"https://s.plings.io?t=s&c={shortcode}"

# Usage example
instance_key = "5Kd3NBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s6Jh5G3f2A"  # Solana address
shortcode = PlingsShortcode.generate(instance_key)
print(f"Shortcode: {shortcode}")  # Output: "5Kd3" (first 4 chars)
print(f"URL: {PlingsShortcode.resolve_to_url(shortcode)}")  # https://s.plings.io?t=s&c=5Kd3

Multi-Identifier Integration

Combined Implementation Strategy

class PlingsMultiIdentifier:
    """
    Manages objects with multiple identifier types for redundancy
    """
    
    def __init__(self, path: str, instance_key: str):
        self.path = path
        self.instance_key = instance_key
        self.identifiers = {}
    
    def add_qr_code(self, size_mm: int = 15) -> dict:
        """Add QR code identifier"""
        url = f"https://s.plings.io?t=q&i={self.instance_key}&p={self.path}"
        qr_data = generate_plings_qr(url, size_mm)
        
        self.identifiers['qr'] = {
            "type": "QR_CODE",
            "url": url,
            "image_data": qr_data,
            "size_mm": size_mm,
            "priority": 1  # Primary identifier
        }
        return self.identifiers['qr']
    
    def add_nfc_tag(self, tag_type: str = "NTAG213") -> dict:
        """Add NFC tag identifier"""
        nfc_data = program_nfc_tag(self.path, self.instance_key, tag_type)
        
        self.identifiers['nfc'] = {
            "type": "NFC_TAG",
            "ndef_message": nfc_data['ndef_message'],
            "tag_type": tag_type,
            "priority": 2  # Secondary identifier
        }
        return self.identifiers['nfc']
    
    def add_rfid_tag(self) -> dict:
        """Add RFID tag identifier"""
        epc = encode_path_to_epc(self.path, self.instance_key)
        
        self.identifiers['rfid'] = {
            "type": "RFID_UHF", 
            "epc": epc,
            "frequency": "902-928MHz",
            "priority": 3  # Tertiary identifier
        }
        return self.identifiers['rfid']
    
    def add_shortcode(self) -> dict:
        """Add shortcode identifier"""
        shortcode = PlingsShortcode.generate(self.instance_key)
        
        self.identifiers['shortcode'] = {
            "type": "SHORTCODE",
            "code": shortcode,
            "url": f"https://s.plings.io?t=s&c={shortcode}",
            "priority": 4,  # Backup identifier
            "charset": "Base58",
            "collision_resistance": "58^4 = 11,316,496 combinations"
        }
        return self.identifiers['shortcode']
    
    def get_verification_matrix(self) -> dict:
        """
        Return verification matrix for anti-counterfeiting
        All identifiers must resolve to same object
        """
        return {
            "object_path": self.path,
            "instance_key": self.instance_key,
            "identifier_count": len(self.identifiers),
            "verification_urls": [
                ident["url"] for ident in self.identifiers.values() 
                if "url" in ident
            ],
            "anti_counterfeit_hash": hashlib.sha256(
                f"{self.path}:{self.instance_key}:{len(self.identifiers)}".encode()
            ).hexdigest()[:16]
        }

# Example: High-value watch with multiple identifiers
watch = PlingsMultiIdentifier("2.G.2.2.7P.9j", "4K7mX9abDcE", "luxury_watch_instance_key")
watch.add_qr_code(size_mm=8)      # Micro-QR on case back
watch.add_nfc_tag("NTAG216")      # NFC in crown mechanism
watch.add_rfid_tag()              # Hidden RFID in dial
watch.add_shortcode()             # Engraved on movement

verification = watch.get_verification_matrix()

Performance Optimization

Scanning Speed Optimization

class OptimizedScanner:
    """
    Optimize scanning performance across identifier types
    """
    
    def __init__(self):
        self.scan_cache = {}
        self.verification_cache = {}
    
    async def scan_with_fallback(self, timeout_ms: int = 5000) -> dict:
        """
        Attempt multiple scanning methods with intelligent fallback
        """
        scan_tasks = [
            self.try_qr_scan(timeout_ms // 3),
            self.try_nfc_scan(timeout_ms // 3),
            self.try_rfid_scan(timeout_ms // 3)
        ]
        
        # Try all methods concurrently, return first success
        try:
            result = await asyncio.wait_for(
                asyncio.gather(*scan_tasks, return_when=asyncio.FIRST_COMPLETED),
                timeout=timeout_ms / 1000
            )
            return result[0]  # First successful scan
        except asyncio.TimeoutError:
            # Fallback to manual shortcode entry
            return await self.prompt_shortcode_entry()
    
    async def batch_rfid_scan(self, max_tags: int = 100) -> List[dict]:
        """
        Optimized bulk RFID scanning for inventory operations
        """
        reader = PlingsRFIDReader("192.168.1.100")
        tags = reader.read_plings_tags()
        
        # Parallel verification of all tags
        verification_tasks = [
            self.verify_identifier_async(tag) for tag in tags[:max_tags]
        ]
        
        verified_tags = await asyncio.gather(*verification_tasks)
        return [tag for tag in verified_tags if tag['valid']]

For comprehensive identifier type selection guidance, see Physical Identifier Types Overview.

Technical Implementation

Encoding/Decoding

Base58 Encoding: All cryptographic keys use Base58 encoding

  • Avoids confusing characters (0, O, I, l)
  • Maintains readability in URLs
  • Standard across cryptocurrency ecosystems
  • Short codes inherit Base58 character set for consistency

URL Encoding: Standard percent-encoding for special characters in path segments

Data Architecture: Stored vs Derivable Fields

Core Principle: Store minimal data, derive maximum value

Fields Stored in Database (when identifier is used):

  • instanceKey: "5Kd3NBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s6Jh5G3f2A" (Base58-encoded unique identifier)
  • path: "2.3j.4.5Q.3L8z" (hierarchical path) + class_pointer: "4K7mX9abDcE"
  • status: "active" (usage status)
  • first_scan_at: "2024-01-15T10:30:00Z" (when first used)

Fields Always Derivable (never stored):

  • shortCode: instanceKey.substring(0, 4)"5Kd3" (first 4 characters)
  • qrCodeUrl: f"https://s.plings.io?t=q&i={instanceKey}&p={path}" (constructed URL)
  • publicKey: Resolved via manufacturer registry using path components

PlingsIdentifier Database Schema:

{
  "id": "PlingsIdentifier/abc123",
  "instanceKey": "5Kd3NBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s6Jh5G3f2A",
  "path": "2.3j.4.5Q.3L8z",
  "class_pointer": "4K7mX9abDcE",
  "status": "active",
  "first_scan_at": "2024-01-15T10:30:00Z"
}

Relationships:

  • (:PlingsIdentifier)-[:IDENTIFIES]->(:ObjectInstance)
  • (:PlingsIdentifier)-[:DERIVED_FROM]->(:ObjectClass) (via class_key)

Batch Printing: Temporary Data Generation

Nomenclature: Tags = Identifiers = Physical QR stickers used to identify objects

Production Workflow for Physical Tags:

def generate_batch_for_printing(manufacturer_id: int, start_index: int, count: int) -> str:
    """
    Generate CSV file for physical tag printing - temporary data only.
    Data is destroyed after printing, only batch record retained.
    """
    batch_data = []
    path_base = f"{manufacturer_id}.{plant_id}.C{class_id}.{batch_id}"
    
    for i in range(start_index, start_index + count):
        # Generate identifier (no database storage)
        # CRITICAL: derive_instance_key returns Solana address for direct payments
        instance_key = derive_instance_key(manufacturer_key, f"{path_base}.{i}")
        
        # Derive all fields for printing
        batch_data.append({
            "instanceKey": instance_key,
            "shortCode": instance_key[:4],
            "qrCodeUrl": f"https://s.plings.io?t=q&i={instance_key}&p={path_base}.{i}",
            "sequence": i
        })
    
    # Generate temporary CSV for printing
    csv_file = create_temporary_csv(batch_data)
    
    # Record batch generation (permanent record)
    log_batch_printed(
        manufacturer_id=manufacturer_id,
        range_start=start_index,
        range_end=start_index + count - 1,
        printed_at=datetime.now(),
        printed_by=user_id
    )
    
    return csv_file  # File deleted after printing

Batch Records (Permanent):

{
  "batch_id": "batch_2024_001",
  "manufacturer_id": 3,
  "identifier_range": "1000-1999", 
  "printed_at": "2024-01-15T14:30:00Z",
  "printed_by": "user_456",
  "count": 1000
}

Key Principle: Individual identifiers are NOT stored during manufacturing - only batch metadata for accountability.

PublicKey Resolution: Path-to-Registry Lookup

Critical Distinction: The identifier itself contains only instanceKey and path. The publicKey for verification is resolved through registry lookup, not stored in the identifier.

Resolution Process:

async function resolvePublicKey(instanceKey, path) {
    // Step 1: Parse path components
    const pathParts = path.split('.');
    const manufacturerId = parseInt(pathParts[0]);  // e.g., 3 = Coca-Cola
    
    // Step 2: Identify verification anchor
    let anchorPath, registryKey;
    if (manufacturerId === 1) {
        // Plings generic tag
        anchorPath = "m/44'/501'/1'";
        registryKey = "plings_master";
    } else {
        // Manufacturer-issued tag  
        anchorPath = `m/44'/501'/${manufacturerId}'`;
        registryKey = manufacturerId.toString();
    }
    
    // Step 3: Query manufacturer registry
    const registry = await getManufacturerRegistry();
    const publicKey = registry[registryKey];
    
    if (!publicKey) {
        throw new Error(`Manufacturer ${manufacturerId} not found in registry`);
    }
    
    return {
        publicKey: publicKey,
        anchorPath: anchorPath,
        registrySource: manufacturerId === 1 ? "plings" : "manufacturer"
    };
}

Registry API Structure:

{
  "1": "5Kd3NBUoD3fCRJcyPbTWTBf2n8Z4Vt9u2K7s6Jh5G3f2A",  // Plings master
  "3": "7Mx5JCLoE2gHRJdyQcUWVCg3o9A5Wu8v3K8t7Kj6H4g3B",  // Coca-Cola
  "17": "9Nx7MDPoG4iJTLeaSfYWXEi5q1C7Yv0x5M0v9Ml8J6i5D"  // IKEA
}

Client Implementation Example:

class PlingsVerifier {
    async verifyIdentifier(instanceKey, path) {
        // Resolve publicKey from path
        const {publicKey, anchorPath} = await this.resolvePublicKey(instanceKey, path);
        
        // Perform cryptographic verification
        return this.verifyCryptographicDerivation(instanceKey, publicKey, path);
    }
    
    async getManufacturerRegistry() {
        // Cache registry locally, refresh periodically
        if (!this.registryCache || this.isCacheExpired()) {
            const response = await fetch('https://api.plings.io/v1/manufacturers');
            this.registryCache = await response.json();
            this.cacheTimestamp = Date.now();
        }
        return this.registryCache;
    }
}

Key Points:

  • Identifier contains NO publicKey directly
  • PublicKey resolution requires network access to registry (first time)
  • Registry can be cached for offline verification
  • Path determines which manufacturer registry entry to use

API Endpoints

Identifier Resolution:

GET /api/resolve?i=<instance_key>&p=<path>

Response:
{
  "instanceKey": "5Kd3NBUoD3fCRJc...",     // Stored field
  "path": "2.3j.4.5Q.3L8z",
  "class_pointer": "4K7mX9abDcE",        // Stored field  
  "shortCode": "5Kd3",                    // 🔄 DERIVED: instanceKey.substring(0,4)
  "qrCodeUrl": "https://s.plings.io?...", // 🔄 DERIVED: URL construction
  "publicKey": "7Mx5JCLoE2gHRJd...",      // 🔄 RESOLVED: via registry lookup
  "verified": true,                       // 🔄 COMPUTED: cryptographic verification
  "manufacturer": "Coca-Cola"             // 🔄 RESOLVED: from path + registry
}

Batch Generation for Manufacturing:

POST /api/batch/generate
Content-Type: application/json

{
  "manufacturer_id": 3,
  "start_index": 1000, 
  "count": 1000,
  "class_id": 2,
  "batch_id": "5847"
}

Response:
{
  "csv_download_url": "/tmp/batch_2024_001.csv",  // ⏱️ TEMPORARY: deleted after download
  "batch_record": {
    "batch_id": "batch_2024_001",
    "range": "1000-1999",
    "created_at": "2024-01-15T14:30:00Z"
  }
}

// CSV contains derived fields for printing only:
// instanceKey,shortCode,qrCodeUrl,sequence

Verification:

POST /api/verify
Content-Type: application/json

{
  "instance_key": "5Kd3NBUo...",
  "path": "2.3j.4.5Q.3L8z",
  "class_pointer": "4K7mX9abDcE"
}

Response:
{
  "valid": true,                          // 🔄 COMPUTED: cryptographic verification
  "manufacturer": "Coca-Cola",            // 🔄 RESOLVED: from path + registry
  "product_class": "Classic Coke 355ml", // 🔄 RESOLVED: from class pointer + registry
  "publicKey": "7Mx5JCLoE2gHRJd...",      // 🔄 RESOLVED: registry lookup
  "verification_method": "manufacturer_anchor"
}

PublicKey Registry Access:

GET /api/manufacturers/registry

Response:
{
  "1": "5Kd3NBUoD3fCRJc...",    // Plings master key
  "3": "7Mx5JCLoE2gHRJd...",    // Coca-Cola anchor key  
  "17": "9Nx7MDPoG4iJTLe...",   // IKEA anchor key
  "last_updated": "2024-01-15T10:00:00Z"
}

Field Legend:

  • Stored: Retrieved from database
  • 🔄 DERIVED: Computed from stored fields
  • 🔄 RESOLVED: Looked up via registry/external source
  • 🔄 COMPUTED: Calculated through algorithms
  • ⏱️ TEMPORARY: Generated for immediate use, not stored

Integration Examples

QR Code Generation

import qrcode
from plings.crypto import generate_instance_key

def create_product_qr(class_key, path, index):
    # Derive instance key from class key
    instance_key = generate_instance_key(class_key, index)
    
    # Build URL
    url = f"https://scan.plings.io/s?t=q&cp={class_key}&i={instance_key}&p={path}"
    
    # Generate QR code
    qr = qrcode.QRCode(version=1, box_size=10, border=5)
    qr.add_data(url)
    qr.make(fit=True)
    
    return qr.make_image(fill_color="black", back_color="white")

NFC Tag Programming

// NFC Web API example
const nfcWriter = new NDEFWriter();

async function writeNFCTag(classKey, instanceKey, path) {
  const url = `https://scan.plings.io/s?t=n&cp=${classKey}&i=${instanceKey}&p=${path}`;
  
  try {
    await nfcWriter.write([{
      recordType: "url",
      data: url
    }]);
    console.log("NFC tag programmed successfully");
  } catch (error) {
    console.error("NFC write failed:", error);
  }
}

Mobile App Integration

// iOS Core NFC framework
import CoreNFC

class PlingsNFCReader: NSObject, NFCNDEFReaderSessionDelegate {
    func startNFCReading() {
        let session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
        session.alertMessage = "Hold near Plings NFC tag"
        session.begin()
    }
    
    func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        guard let record = messages.first?.records.first,
              let url = record.wellKnownTypeURIPayload() else { return }
        
        // Parse Plings URL and resolve identifier
        PlingsResolver.resolve(url: url) { result in
            // Handle object instance data
        }
    }
}

Set Object Identifier Constraints

Physical vs Logical Object Limitation

PlingsIdentifiers can only be attached to physical objects that have a tangible, scannable presence. This creates an important constraint for Set Objects:

Valid Identifier Attachments ✅

// Individual components can have identifiers
(:LeftSkiGlove)-[:IDENTIFIED_BY]->(:QRCode1)
(:RightSkiGlove)-[:IDENTIFIED_BY]->(:QRCode2)
(:Hammer)-[:IDENTIFIED_BY]->(:NFCTag1)
(:Screwdriver)-[:IDENTIFIED_BY]->(:QRCode3)

Invalid Identifier Attachments ❌

// Set objects cannot have direct identifiers
// (:SkiGloveSet)-[:IDENTIFIED_BY]->(:QRCode)     // INVALID
// (:ToolSet)-[:IDENTIFIED_BY]->(:NFCTag)        // INVALID

Set Object Access Pattern

Since Set Objects cannot have direct identifiers, they must be accessed through component scanning workflows:

  1. Scan any component that has a PlingsIdentifier
  2. System detects component is part of a set
  3. User chooses operation scope (component-only vs entire set)
  4. Execute operation based on user choice

Example: Ski Glove Set Access

1. Scan QR code on left glove
2. System shows: "Left Ski Glove (part of My Ski Gloves set)"
3. User chooses: "Work with entire set" 
4. Scan target location
5. System moves both gloves to new location

Validation Rules

The system must enforce these validation rules:

Database Constraints:

  • Set Objects (objects with PART_OF children) cannot have IDENTIFIED_BY relationships
  • Only leaf objects (no PART_OF children) or individual components can have identifiers

API Validation:

// Check if object can have identifier before creating
MATCH (obj:ObjectInstance {id: $objectId})
OPTIONAL MATCH (obj)<-[:PART_OF]-()
WITH obj, COUNT(*) as hasComponents
WHERE hasComponents = 0  // Only if no components
// Proceed with identifier creation

UI Validation:

  • Identifier assignment interfaces should not show Set Objects as options
  • Component scanning should detect and handle set membership appropriately

Scanning Workflow Implementation

GraphQL Query for Component Detection:

mutation scanIdentifier($identifierKey: String!) {
  scanResult(identifierKey: $identifierKey) {
    component {
      id
      name
    }
    parentSet {
      id 
      name
    }
    hasSet
    availableActions
  }
}

Response Handling:

  • If hasSet = true: Show set operation options
  • If hasSet = false: Proceed with component-only operations

Error Handling

Common Error Scenarios:

  • Attempting to assign identifier to Set Object → Validation error
  • Scanning broken/invalid identifier → Graceful degradation
  • Set component missing → Show partial set status

Error Messages:

  • “Set objects cannot have physical identifiers. Please tag individual components instead.”
  • “Component X is part of set Y. Choose operation scope to continue.”
  • “Set Y is incomplete. Component Z could not be located.”

Security Considerations

Key Management

  • Master keys stored in hardware security modules (HSMs)
  • Manufacturer keys distributed via secure key ceremony
  • AnchorKeys published in immutable ledger for verification
  • InstanceKeys generated deterministically, never stored

Threat Mitigation

Threat Mitigation
Key compromise Hardened derivation limits exposure scope
Counterfeit products Dual identifier + OTP verification
Replay attacks One-time-use flags + blockchain monitoring
Physical cloning Tamper-evident seals + hidden identifiers
Network attacks Offline verification capability

Privacy Protection

  • Minimal data exposure in URLs (only cryptographic identifiers)
  • User consent required for location tracking
  • Selective disclosure based on scanning context
  • Right to be forgotten through identifier deactivation

Future Extensions

Planned Features

  • Multi-signature support for shared ownership scenarios
  • Time-locked identifiers for rental/lease applications
  • Batch operations for bulk identifier generation
  • Cross-chain support for multiple blockchain networks
  • Quantum-resistant cryptography migration path

Integration Opportunities

  • IoT sensors for automated status updates
  • Supply chain tracking with logistics partners
  • Insurance claims with immutable audit trails
  • Carbon footprint tracking throughout product lifecycle
  • Circular economy platforms for repair/resale/recycling

This specification is versioned and maintained by the Plings development team. For implementation questions or feature requests, please refer to the project’s GitHub repository or contact the core development team.

Last Updated: Lör 12 Jul 2025 22:06:07 CEST - MAJOR: Added comprehensive direct object commerce documentation. Explained how Plings revolutionizes retail by enabling objects to receive payments directly via blockchain, eliminating traditional POS infrastructure. Detailed service marketplace capabilities and Solana implementation advantages.