Code Examples

Complete, working code examples for implementing DSPIP shipping protocol. Copy and adapt these samples for your application.

Jump to Example

Key Generation

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
}

Create SHIP Payload

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 & Generate QR Data

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
}

Verify Signature

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
}

DNS Lookup

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
}

Key Lifecycle

Parse and validate key lifecycle fields from DNS records to ensure keys are active and not expired.

Key Lifecycle States

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"])

Privacy Modes

DSPIP supports three privacy modes: standard, encrypted, and split-key. Here's how to implement encrypted mode with ECIES.

Privacy Mode Selection

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())

Complete Flow Example

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);
Ready to Implement?

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.