Complete, working code examples for implementing DSPIP shipping protocol. Copy and adapt these samples for your application.
Generate a secp256k1 key pair for signing SHIP payloads. The public key is published to DNS for verification.
// Key generation with @noble/secp256k1 import * as secp from '@noble/secp256k1'; import { randomBytes } from 'crypto'; // Generate random 32-byte private key const privateKey = randomBytes(32); // Derive compressed public key (33 bytes) const publicKey = secp.getPublicKey(privateKey, true); // Convert to hex for storage const privateKeyHex = Buffer.from(privateKey).toString('hex'); const publicKeyHex = Buffer.from(publicKey).toString('hex'); // Convert public key to Base64 for DNS TXT record const publicKeyBase64 = Buffer.from(publicKey).toString('base64'); console.log('Private Key (hex):', privateKeyHex); console.log('Public Key (hex):', publicKeyHex); console.log('Public Key (base64):', publicKeyBase64); // DNS TXT record format with lifecycle fields: // v=DSPIP1; k=ec; c=secp256k1; p={publicKeyBase64}; // t={created}; exp={sign_expires}; exp-v={verify_expires}; // s=active; seq=1; types=SHIP
# Key generation with coincurve from coincurve import PrivateKey import base64 import secrets # Generate random 32-byte private key private_key_bytes = secrets.token_bytes(32) private_key = PrivateKey(private_key_bytes) # Derive compressed public key (33 bytes) public_key = private_key.public_key.format(compressed=True) # Convert to hex for storage private_key_hex = private_key_bytes.hex() public_key_hex = public_key.hex() # Convert public key to Base64 for DNS TXT record public_key_base64 = base64.b64encode(public_key).decode('ascii') print(f'Private Key (hex): {private_key_hex}') print(f'Public Key (hex): {public_key_hex}') print(f'Public Key (base64): {public_key_base64}') # DNS TXT record format with lifecycle fields: # v=DSPIP1; k=ec; c=secp256k1; p={public_key_base64}; # t={created}; exp={sign_expires}; exp-v={verify_expires}; # s=active; seq=1; types=SHIP
// Key generation with go-ethereum/crypto package main import ( "crypto/ecdsa" "encoding/base64" "encoding/hex" "fmt" "github.com/ethereum/go-ethereum/crypto" ) func main() { // Generate random private key privateKey, err := crypto.GenerateKey() if err != nil { panic(err) } // Get private key bytes privateKeyBytes := crypto.FromECDSA(privateKey) privateKeyHex := hex.EncodeToString(privateKeyBytes) // Get compressed public key (33 bytes) publicKey := crypto.CompressPubkey(&privateKey.PublicKey) publicKeyHex := hex.EncodeToString(publicKey) publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKey) fmt.Println("Private Key (hex):", privateKeyHex) fmt.Println("Public Key (hex):", publicKeyHex) fmt.Println("Public Key (base64):", publicKeyBase64) // DNS TXT record format with lifecycle fields: // v=DSPIP1; k=ec; c=secp256k1; p={publicKeyBase64}; // t={created}; exp={sign_expires}; exp-v={verify_expires}; // s=active; seq=1; types=SHIP }
Build a SHIP payload with issuer/subject information. The payload is Base64-encoded before signing.
// Create a SHIP payload function createShipPayload(options) { const payload = { type: "SHIP", issuer: { organization: options.issuerOrg, address: { city: options.issuerCity, state: options.issuerState, country: options.issuerCountry } }, subject: { name: options.recipientName, address: { street1: options.recipientStreet, city: options.recipientCity, state: options.recipientState, postalCode: options.recipientPostal, country: options.recipientCountry } }, itemId: options.trackingNumber, timestamp: Date.now(), typeData: { privacyMode: "standard", carrier: options.carrier, service: options.service } }; // Base64 encode the payload const jsonString = JSON.stringify(payload); const encodedPayload = Buffer.from(jsonString).toString('base64'); return { payload, encodedPayload }; } // Example usage const { payload, encodedPayload } = createShipPayload({ issuerOrg: 'ACME Logistics', issuerCity: 'Omaha', issuerState: 'NE', issuerCountry: 'US', recipientName: 'Bob Jones', recipientStreet: '456 Main Street', recipientCity: 'Lincoln', recipientState: 'NE', recipientPostal: '68501', recipientCountry: 'US', trackingNumber: 'TRACK-2025-000123', carrier: 'ACME', service: 'Ground' });
# Create a SHIP payload import json import base64 import time def create_ship_payload( issuer_org, issuer_city, issuer_state, issuer_country, recipient_name, recipient_street, recipient_city, recipient_state, recipient_postal, recipient_country, tracking_number, carrier, service ): payload = { "type": "SHIP", "issuer": { "organization": issuer_org, "address": { "city": issuer_city, "state": issuer_state, "country": issuer_country } }, "subject": { "name": recipient_name, "address": { "street1": recipient_street, "city": recipient_city, "state": recipient_state, "postalCode": recipient_postal, "country": recipient_country } }, "itemId": tracking_number, "timestamp": int(time.time() * 1000), "typeData": { "privacyMode": "standard", "carrier": carrier, "service": service } } # Base64 encode the payload json_string = json.dumps(payload, separators=(',', ':')) encoded_payload = base64.b64encode(json_string.encode()).decode() return payload, encoded_payload # Example usage payload, encoded = create_ship_payload( issuer_org="ACME Logistics", issuer_city="Omaha", issuer_state="NE", issuer_country="US", recipient_name="Bob Jones", recipient_street="456 Main Street", recipient_city="Lincoln", recipient_state="NE", recipient_postal="68501", recipient_country="US", tracking_number="TRACK-2025-000123", carrier="ACME", service="Ground" )
// Create a SHIP payload package main import ( "encoding/base64" "encoding/json" "time" ) type Address struct { Street1 string `json:"street1,omitempty"` City string `json:"city,omitempty"` State string `json:"state,omitempty"` PostalCode string `json:"postalCode,omitempty"` Country string `json:"country"` } type Party struct { Name string `json:"name,omitempty"` Organization string `json:"organization,omitempty"` Address Address `json:"address"` } type TypeData struct { PrivacyMode string `json:"privacyMode"` Carrier string `json:"carrier,omitempty"` Service string `json:"service,omitempty"` } type ShipPayload struct { Type string `json:"type"` Issuer Party `json:"issuer"` Subject Party `json:"subject"` ItemID string `json:"itemId"` Timestamp int64 `json:"timestamp"` TypeData TypeData `json:"typeData"` } func createShipPayload() (string, error) { payload := ShipPayload{ Type: "SHIP", Issuer: Party{ Organization: "ACME Logistics", Address: Address{City: "Omaha", State: "NE", Country: "US"}, }, Subject: Party{ Name: "Bob Jones", Address: Address{ Street1: "456 Main Street", City: "Lincoln", State: "NE", PostalCode: "68501", Country: "US", }, }, ItemID: "TRACK-2025-000123", Timestamp: time.Now().UnixMilli(), TypeData: TypeData{PrivacyMode: "standard", Carrier: "ACME", Service: "Ground"}, } jsonBytes, err := json.Marshal(payload) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(jsonBytes), nil }
Sign the payload with ECDSA and create the complete 6-field QR data string.
// Sign payload and generate QR data import * as secp from '@noble/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; async function signAndGenerateQR(privateKeyHex, keyLocator, encodedPayload) { const version = '1.0'; const type = 'SHIP'; // Construct signable content (5 fields) const signable = `DSPIP|${version}|${type}|${keyLocator}|${encodedPayload}`; // Hash the signable content const msgHash = sha256(signable); // Sign with private key const privateKey = Buffer.from(privateKeyHex, 'hex'); const signature = await secp.signAsync(msgHash, privateKey); // Encode signature as DER hex const signatureHex = signature.toDERHex(); // Build complete QR data (6 fields) const qrData = `DSPIP|${version}|${type}|${keyLocator}|${encodedPayload}|${signatureHex}`; return { qrData, signature: signatureHex, signable }; } // Example usage const result = await signAndGenerateQR( 'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35', 'warehouse._dspip.example.com', encodedPayload ); console.log('QR Data:', result.qrData); console.log('Signature:', result.signature);
# Sign payload and generate QR data from coincurve import PrivateKey import hashlib def sign_and_generate_qr(private_key_hex, key_locator, encoded_payload): version = "1.0" type_field = "SHIP" # Construct signable content (5 fields) signable = f"DSPIP|{version}|{type_field}|{key_locator}|{encoded_payload}" # Hash the signable content msg_hash = hashlib.sha256(signable.encode()).digest() # Sign with private key private_key = PrivateKey(bytes.fromhex(private_key_hex)) signature = private_key.sign(msg_hash, hasher=None) # Encode signature as hex signature_hex = signature.hex() # Build complete QR data (6 fields) qr_data = f"DSPIP|{version}|{type_field}|{key_locator}|{encoded_payload}|{signature_hex}" return { "qrData": qr_data, "signature": signature_hex, "signable": signable } # Example usage result = sign_and_generate_qr( "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", "warehouse._dspip.example.com", encoded_payload ) print("QR Data:", result["qrData"])
// Sign payload and generate QR data package main import ( "crypto/sha256" "encoding/hex" "fmt" "github.com/ethereum/go-ethereum/crypto" ) func signAndGenerateQR(privateKeyHex, keyLocator, encodedPayload string) (string, error) { version := "1.0" typeField := "SHIP" // Construct signable content (5 fields) signable := fmt.Sprintf("DSPIP|%s|%s|%s|%s", version, typeField, keyLocator, encodedPayload) // Hash the signable content hash := sha256.Sum256([]byte(signable)) // Load private key privateKeyBytes, _ := hex.DecodeString(privateKeyHex) privateKey, _ := crypto.ToECDSA(privateKeyBytes) // Sign the hash signature, err := crypto.Sign(hash[:], privateKey) if err != nil { return "", err } // Convert to DER format (simplified - use proper DER encoding) signatureHex := hex.EncodeToString(signature) // Build complete QR data (6 fields) qrData := fmt.Sprintf("DSPIP|%s|%s|%s|%s|%s", version, typeField, keyLocator, encodedPayload, signatureHex) return qrData, nil }
Parse QR data, fetch the public key from DNS, and verify the ECDSA signature.
// Verify DSPIP signature import * as secp from '@noble/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; import dns from 'dns'; async function verifyDspip(qrData) { // Step 1: Parse QR data (6-7 fields) const parts = qrData.split('|'); if (parts.length < 6 || parts.length > 7) { throw new Error(`Invalid format: expected 6-7 fields, got ${parts.length}`); } const [protocol, version, type, keyLocator, encodedPayload, signatureHex] = parts; // Step 2: Validate protocol and type if (protocol !== 'DSPIP') throw new Error('Invalid protocol'); if (type !== 'SHIP') throw new Error('Invalid type'); // Step 3: Decode payload const payloadJson = Buffer.from(encodedPayload, 'base64').toString(); const payload = JSON.parse(payloadJson); // Step 4: Fetch public key from DNS const dnsRecord = await lookupDnsTxt(keyLocator); const publicKeyBase64 = parseDnsRecord(dnsRecord).p; const publicKey = Buffer.from(publicKeyBase64, 'base64'); // Step 5: Reconstruct signable content const signable = `${protocol}|${version}|${type}|${keyLocator}|${encodedPayload}`; const msgHash = sha256(signable); // Step 6: Verify signature const signature = secp.Signature.fromDER(signatureHex); const isValid = secp.verify(signature, msgHash, publicKey); return { valid: isValid, type, payload, issuer: payload.issuer, subject: payload.subject, itemId: payload.itemId }; } // Example usage const result = await verifyDspip(qrData); if (result.valid) { console.log('Signature verified!'); console.log('Issuer:', result.issuer.organization); console.log('Item ID:', result.itemId); }
# Verify DSPIP signature from coincurve import PublicKey import hashlib import base64 import json import dns.resolver def verify_dspip(qr_data): # Step 1: Parse QR data (6-7 fields) parts = qr_data.split('|') if not (6 <= len(parts) <= 7): raise ValueError(f"Invalid format: expected 6-7 fields, got {len(parts)}") protocol, version, type_field, key_locator, encoded_payload, signature_hex = parts[:6] # Step 2: Validate protocol and type if protocol != 'DSPIP': raise ValueError('Invalid protocol') if type_field != 'SHIP': raise ValueError('Invalid type') # Step 3: Decode payload payload_json = base64.b64decode(encoded_payload).decode() payload = json.loads(payload_json) # Step 4: Fetch public key from DNS dns_record = lookup_dns_txt(key_locator) parsed = parse_dns_record(dns_record) public_key_bytes = base64.b64decode(parsed['p']) # Step 5: Reconstruct signable content signable = f"{protocol}|{version}|{type_field}|{key_locator}|{encoded_payload}" msg_hash = hashlib.sha256(signable.encode()).digest() # Step 6: Verify signature signature = bytes.fromhex(signature_hex) public_key = PublicKey(public_key_bytes) is_valid = public_key.verify(signature, msg_hash, hasher=None) return { "valid": is_valid, "type": type_field, "payload": payload, "issuer": payload.get("issuer"), "itemId": payload.get("itemId") }
// Verify DSPIP signature package main import ( "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "errors" "strings" "github.com/ethereum/go-ethereum/crypto" ) func verifyDspip(qrData string) (bool, *ShipPayload, error) { // Step 1: Parse QR data (6-7 fields) parts := strings.Split(qrData, "|") if len(parts) < 6 || len(parts) > 7 { return false, nil, errors.New("invalid field count") } protocol, version, typeField := parts[0], parts[1], parts[2] keyLocator, encodedPayload, signatureHex := parts[3], parts[4], parts[5] // Step 2: Validate protocol and type if protocol != "DSPIP" || typeField != "SHIP" { return false, nil, errors.New("invalid protocol or type") } // Step 3: Decode payload payloadBytes, _ := base64.StdEncoding.DecodeString(encodedPayload) var payload ShipPayload json.Unmarshal(payloadBytes, &payload) // Step 4: Fetch public key from DNS publicKeyBase64, _ := lookupDnsTxt(keyLocator) publicKeyBytes, _ := base64.StdEncoding.DecodeString(publicKeyBase64) // Step 5: Reconstruct signable content signable := fmt.Sprintf("%s|%s|%s|%s|%s", protocol, version, typeField, keyLocator, encodedPayload) hash := sha256.Sum256([]byte(signable)) // Step 6: Verify signature signatureBytes, _ := hex.DecodeString(signatureHex) publicKey, _ := crypto.DecompressPubkey(publicKeyBytes) isValid := crypto.VerifySignature( crypto.CompressPubkey(publicKey), hash[:], signatureBytes[:64], ) return isValid, &payload, nil }
Query DNS TXT records to retrieve public keys for signature verification.
// DNS lookup for DSPIP public key import dns from 'dns'; import { promisify } from 'util'; const resolveTxt = promisify(dns.resolveTxt); async function lookupDspipKey(keyLocator) { // Query DNS TXT record const records = await resolveTxt(keyLocator); // Find DSPIP record (starts with "v=DSPIP1") for (const record of records) { const txt = record.join(''); if (txt.includes('v=DSPIP1')) { return parseDspipRecord(txt); } } throw new Error('DSPIP record not found'); } function parseDspipRecord(txt) { const record = {}; const pairs = txt.split(';').map(p => p.trim()); for (const pair of pairs) { const [key, ...valueParts] = pair.split('='); if (key && valueParts.length) { record[key.trim()] = valueParts.join('=').trim(); } } // Validate required fields if (record.v !== 'DSPIP1') throw new Error('Invalid version'); if (!record.p) throw new Error('Missing public key'); if (record.types && !record.types.includes('SHIP')) { throw new Error('Key does not support SHIP type'); } return record; } // Example usage const record = await lookupDspipKey('warehouse._dspip.example.com'); console.log('Public Key:', record.p); console.log('Types:', record.types);
# DNS lookup for DSPIP public key import dns.resolver def lookup_dspip_key(key_locator): # Query DNS TXT record answers = dns.resolver.resolve(key_locator, 'TXT') # Find DSPIP record for rdata in answers: txt = str(rdata).strip('"') if 'v=DSPIP1' in txt: return parse_dspip_record(txt) raise ValueError('DSPIP record not found') def parse_dspip_record(txt): record = {} pairs = [p.strip() for p in txt.split(';')] for pair in pairs: if '=' in pair: key, value = pair.split('=', 1) record[key.strip()] = value.strip() # Validate required fields if record.get('v') != 'DSPIP1': raise ValueError('Invalid version') if 'p' not in record: raise ValueError('Missing public key') if 'types' in record and 'SHIP' not in record['types']: raise ValueError('Key does not support SHIP type') return record # Example usage record = lookup_dspip_key('warehouse._dspip.example.com') print('Public Key:', record['p'])
// DNS lookup for DSPIP public key package main import ( "errors" "net" "strings" ) type DspipRecord struct { Version string KeyType string Curve string PublicKey string Types string } func lookupDspipKey(keyLocator string) (*DspipRecord, error) { // Query DNS TXT record records, err := net.LookupTXT(keyLocator) if err != nil { return nil, err } // Find DSPIP record for _, record := range records { if strings.Contains(record, "v=DSPIP1") { return parseDspipRecord(record) } } return nil, errors.New("DSPIP record not found") } func parseDspipRecord(txt string) (*DspipRecord, error) { record := &DspipRecord{} pairs := strings.Split(txt, ";") for _, pair := range pairs { pair = strings.TrimSpace(pair) parts := strings.SplitN(pair, "=", 2) if len(parts) != 2 { continue } key, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) switch key { case "v": record.Version = value case "k": record.KeyType = value case "c": record.Curve = value case "p": record.PublicKey = value case "types": record.Types = value } } if record.Version != "DSPIP1" { return nil, errors.New("invalid version") } if record.PublicKey == "" { return nil, errors.New("missing public key") } return record, nil }
Parse and validate key lifecycle fields from DNS records to ensure keys are active and not expired.
Active: Key can sign new packages and verify signatures
Verify-Only: Key can only verify existing signatures (s=verify-only)
Expired: Key is no longer valid (current time > exp-v)
// Parse and validate key lifecycle from DNS record function parseKeyLifecycle(dnsRecord) { const parsed = parseDspipRecord(dnsRecord); const now = Math.floor(Date.now() / 1000); return { created: parsed.t ? parseInt(parsed.t) : null, signExpires: parsed.exp ? parseInt(parsed.exp) : null, verifyExpires: parsed['exp-v'] ? parseInt(parsed['exp-v']) : null, status: parsed.s || 'active', sequence: parsed.seq ? parseInt(parsed.seq) : 1, publicKey: parsed.p }; } function validateKeyForSigning(lifecycle) { const now = Math.floor(Date.now() / 1000); if (lifecycle.status === 'revoked') { return { valid: false, error: 'KEY_REVOKED' }; } if (lifecycle.status === 'verify-only') { return { valid: false, error: 'KEY_VERIFY_ONLY' }; } if (lifecycle.signExpires && now > lifecycle.signExpires) { return { valid: false, error: 'KEY_SIGNING_EXPIRED' }; } return { valid: true }; } function validateKeyForVerification(lifecycle, signatureTimestamp) { const now = Math.floor(Date.now() / 1000); if (lifecycle.status === 'revoked') { return { valid: false, error: 'KEY_REVOKED' }; } if (lifecycle.verifyExpires && now > lifecycle.verifyExpires) { return { valid: false, error: 'KEY_VERIFICATION_EXPIRED' }; } // Check if signature was made before key was created if (lifecycle.created && signatureTimestamp < lifecycle.created) { return { valid: false, error: 'SIGNATURE_BEFORE_KEY_CREATED' }; } return { valid: true }; } // Example: Check key before signing const record = await lookupDspipKey('warehouse._dspip.example.com'); const lifecycle = parseKeyLifecycle(record); const canSign = validateKeyForSigning(lifecycle); if (!canSign.valid) { console.error('Cannot sign:', canSign.error); }
# Parse and validate key lifecycle from DNS record import time def parse_key_lifecycle(dns_record): parsed = parse_dspip_record(dns_record) return { "created": int(parsed.get("t", 0)) or None, "sign_expires": int(parsed.get("exp", 0)) or None, "verify_expires": int(parsed.get("exp-v", 0)) or None, "status": parsed.get("s", "active"), "sequence": int(parsed.get("seq", 1)), "public_key": parsed["p"] } def validate_key_for_signing(lifecycle): now = int(time.time()) if lifecycle["status"] == "revoked": return {"valid": False, "error": "KEY_REVOKED"} if lifecycle["status"] == "verify-only": return {"valid": False, "error": "KEY_VERIFY_ONLY"} if lifecycle["sign_expires"] and now > lifecycle["sign_expires"]: return {"valid": False, "error": "KEY_SIGNING_EXPIRED"} return {"valid": True} def validate_key_for_verification(lifecycle, signature_timestamp): now = int(time.time()) if lifecycle["status"] == "revoked": return {"valid": False, "error": "KEY_REVOKED"} if lifecycle["verify_expires"] and now > lifecycle["verify_expires"]: return {"valid": False, "error": "KEY_VERIFICATION_EXPIRED"} # Check if signature was made before key was created if lifecycle["created"] and signature_timestamp < lifecycle["created"]: return {"valid": False, "error": "SIGNATURE_BEFORE_KEY_CREATED"} return {"valid": True} # Example: Check key before signing record = lookup_dspip_key("warehouse._dspip.example.com") lifecycle = parse_key_lifecycle(record) can_sign = validate_key_for_signing(lifecycle) if not can_sign["valid"]: print("Cannot sign:", can_sign["error"])
DSPIP supports three privacy modes: standard, encrypted, and split-key. Here's how to implement encrypted mode with ECIES.
Standard: Full recipient visible (B2B)
Encrypted: Recipient encrypted for last mile provider (consumer privacy)
Split-Key: Physical anti-cloning with Ed25519 (high-value)
// Encrypted privacy mode - encrypt recipient for last mile provider import { encrypt, decrypt } from 'eciesjs'; async function createEncryptedPayload(options) { // Recipient data to encrypt for last mile provider const recipientData = { name: options.recipientName, address: { street1: options.recipientStreet, city: options.recipientCity, state: options.recipientState, postalCode: options.recipientPostal, country: options.recipientCountry }, deliveryInstructions: options.instructions }; // Encrypt with LMP's public key (ECIES) const recipientJson = JSON.stringify(recipientData); const encrypted = encrypt(options.lmpPublicKey, Buffer.from(recipientJson)); const encryptedRecipient = encrypted.toString('base64'); // Build payload with encrypted recipient const payload = { type: "SHIP", issuer: { organization: options.issuerOrg, address: { country: options.issuerCountry } }, subject: { // Only LMP reference visible - no actual recipient lastMileProvider: options.lmpKeyLocator }, itemId: options.trackingNumber, timestamp: Date.now(), typeData: { privacyMode: "encrypted", lastMileProvider: options.lmpKeyLocator, encryptedRecipient: encryptedRecipient } }; return payload; } // Last Mile Provider decrypts recipient function decryptRecipient(encryptedRecipient, lmpPrivateKey) { const encrypted = Buffer.from(encryptedRecipient, 'base64'); const decrypted = decrypt(lmpPrivateKey, encrypted); return JSON.parse(decrypted.toString()); }
# Encrypted privacy mode - encrypt recipient for last mile provider from ecies import encrypt, decrypt import json import base64 def create_encrypted_payload( issuer_org, issuer_country, recipient_name, recipient_street, recipient_city, recipient_state, recipient_postal, recipient_country, tracking_number, lmp_key_locator, lmp_public_key, instructions=None ): # Recipient data to encrypt for last mile provider recipient_data = { "name": recipient_name, "address": { "street1": recipient_street, "city": recipient_city, "state": recipient_state, "postalCode": recipient_postal, "country": recipient_country } } if instructions: recipient_data["deliveryInstructions"] = instructions # Encrypt with LMP's public key (ECIES) recipient_json = json.dumps(recipient_data) encrypted = encrypt(lmp_public_key, recipient_json.encode()) encrypted_recipient = base64.b64encode(encrypted).decode() # Build payload with encrypted recipient payload = { "type": "SHIP", "issuer": { "organization": issuer_org, "address": {"country": issuer_country} }, "subject": { # Only LMP reference visible "lastMileProvider": lmp_key_locator }, "itemId": tracking_number, "timestamp": int(time.time() * 1000), "typeData": { "privacyMode": "encrypted", "lastMileProvider": lmp_key_locator, "encryptedRecipient": encrypted_recipient } } return payload # Last Mile Provider decrypts recipient def decrypt_recipient(encrypted_recipient, lmp_private_key): encrypted = base64.b64decode(encrypted_recipient) decrypted = decrypt(lmp_private_key, encrypted) return json.loads(decrypted.decode())
A complete end-to-end example showing key generation, payload creation, signing, and verification.
// Complete DSPIP flow example import * as secp from '@noble/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; import { randomBytes } from 'crypto'; async function dspipDemo() { // ═══════════════════════════════════════════════════════════ // STEP 1: Generate key pair (done once, publish to DNS) // ═══════════════════════════════════════════════════════════ console.log('Step 1: Generating key pair...'); const privateKey = randomBytes(32); const publicKey = secp.getPublicKey(privateKey, true); const privateKeyHex = Buffer.from(privateKey).toString('hex'); const publicKeyBase64 = Buffer.from(publicKey).toString('base64'); console.log('Private Key (keep secret!):', privateKeyHex); console.log('Public Key (for DNS):', publicKeyBase64); // DNS record to publish (with lifecycle fields): // warehouse._dspip.yourdomain.com TXT "v=DSPIP1; k=ec; c=secp256k1; // p={publicKeyBase64}; t={now}; exp={+1yr}; exp-v={+2yr}; // s=active; seq=1; types=SHIP" // ═══════════════════════════════════════════════════════════ // STEP 2: Create SHIP payload // ═══════════════════════════════════════════════════════════ console.log('\nStep 2: Creating SHIP payload...'); const payload = { type: "SHIP", issuer: { organization: "ACME Logistics", address: { city: "Omaha", state: "NE", country: "US" } }, subject: { name: "Bob Jones", address: { street1: "456 Main Street", city: "Lincoln", state: "NE", postalCode: "68501", country: "US" } }, itemId: "TRACK-2025-000123", timestamp: Date.now(), typeData: { privacyMode: "standard", carrier: "ACME", service: "Ground" } }; const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64'); console.log('Encoded payload:', encodedPayload.substring(0, 50) + '...'); // ═══════════════════════════════════════════════════════════ // STEP 3: Sign and generate QR data // ═══════════════════════════════════════════════════════════ console.log('\nStep 3: Signing payload...'); const keyLocator = 'warehouse._dspip.yourdomain.com'; const signable = `DSPIP|1.0|SHIP|${keyLocator}|${encodedPayload}`; const msgHash = sha256(signable); const signature = await secp.signAsync(msgHash, privateKey); const signatureHex = signature.toDERHex(); const qrData = `DSPIP|1.0|SHIP|${keyLocator}|${encodedPayload}|${signatureHex}`; console.log('QR Data length:', qrData.length, 'bytes'); // ═══════════════════════════════════════════════════════════ // STEP 4: Verify signature (scanner side) // ═══════════════════════════════════════════════════════════ console.log('\nStep 4: Verifying signature...'); // Parse QR data const parts = qrData.split('|'); const [protocol, version, type, loc, encPayload, sig] = parts; // Reconstruct and verify const verifySignable = `${protocol}|${version}|${type}|${loc}|${encPayload}`; const verifyHash = sha256(verifySignable); const verifySig = secp.Signature.fromDER(sig); const isValid = secp.verify(verifySig, verifyHash, publicKey); console.log('Signature valid:', isValid ? 'YES ✓' : 'NO ✗'); // Decode payload const decodedPayload = JSON.parse(Buffer.from(encPayload, 'base64').toString()); console.log('Type:', decodedPayload.type); console.log('Issuer:', decodedPayload.issuer.organization); console.log('Item ID:', decodedPayload.itemId); return { qrData, isValid }; } dspipDemo().catch(console.error);
These examples provide a foundation for DSPIP implementation. For production use, ensure proper error handling, key storage security, and DNS record configuration. See the full documentation for complete specifications.