| ← 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.
Quick Links
- Why These Design Choices? → Overview Document
- Physical Identifier Types → Overview Document
- Use Cases & Scenarios → Overview Document
- Implementation Guide → Physical Implementation
- Path Registry → Path Registry Spec
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:
- HD wallet path for hierarchical organization and offline verification
- Native Solana address capable of receiving payments and executing smart contracts
- 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 backendnfc.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
ObjectClassin 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
iparameter 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)
- Extract identifiers from URL parameters
c(ClassKey),i(InstanceKey), andp(path) - Parse path to identify manufacturer (e.g.,
2.1.2.1→ IKEA manufacturer2) - Request verification: Send identifiers to Plings API or IKEA’s verification service
- Verification authority: Plings or IKEA uses their private keys to verify the cryptographic chain
- Return attestation: Verification service confirms authenticity and returns manufacturer details
- Result: Trusted authority confirms this is an authentic IKEA product
For Generic Tags (Plings-issued)
- Extract identifiers from URL parameters
i(InstanceKey) andp(path) - Parse path to determine allocation source (e.g.,
1.1.1→ Plings allocation) - Request verification: Send identifiers to Plings verification API
- Plings verification: Plings uses master private key to verify the cryptographic derivation
- 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
- Extract anchor path:
2.3j(Coca-Cola Atlanta plant) - Query registry: “What’s the public key for anchor
2.3j?” - Extract class: Use class pointer
4K7mX9abDcE(Classic Coke) - Verify instance: Check instance
90000derives from anchor key + full path - 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.1for generic sticker batch 1,1.1.2for 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
PlingsIdentifiernodes can haveIDENTIFIESrelationships pointing to the sameObjectInstance - 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:
- Public QR Code - Visible, general information access
is_one_time_use: false- Standard verification process
- Can be safely copied/photographed
- Hidden OTP Code - Concealed, authenticity verification
is_one_time_use: true- Under scratch-off layer or tamper seal
- Single-use verification token
Verification Workflow
Counterfeit Detection
Scenario: Multiple items claiming same identity
- System detects conflicting location reports for same InstanceKey
- Triggers verification challenge to all claiming parties
- OTP verification proves authentic owner
- Flags fraudulent identifiers as “compromised”
- 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:
- Object presents payment options via service parameters in URL
- Customer chooses transaction type (buy, rent, service, insurance)
- Payment goes to object’s Program Derived Address (Solana blockchain)
- Smart contract executes appropriate action (ownership transfer, rental agreement, etc.)
- NFT minted/transferred as proof of transaction
- 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:
- Scan any component that has a PlingsIdentifier
- System detects component is part of a set
- User chooses operation scope (component-only vs entire set)
- 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_OFchildren) cannot haveIDENTIFIED_BYrelationships - Only leaf objects (no
PART_OFchildren) 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.