DSPIP Developer Documentation

Complete reference for implementing the Digital Signing of Physical Items Protocol. Version 1.0 | Internet-Draft: draft-midwestcyber-dspip-01

DSPIP is an open protocol for authenticating physical items using digitally signed QR codes. It enables verification of item origin and chain of custody without centralized infrastructure, using DNS-based public key distribution similar to DKIM for email.

Protocol Status

DSPIP is currently an Internet-Draft submitted to the IETF. The specification is stable for implementation.

Quick Start

1. Install the SDK

2. Generate a Key Pair

import { generateKeyPair } from '@dspip/core';

const { privateKey, publicKey } = generateKeyPair();

// privateKey: 32 bytes (hex encoded)
// publicKey: 33 bytes compressed (base64 for DNS)

3. Publish Your Public Key to DNS

warehouse._dspip.example.com. IN TXT "v=DSPIP1; k=ec; c=secp256k1; p=YOUR_PUBLIC_KEY_BASE64"

4. Create a Signed QR Code

import { createSignedQR } from '@dspip/core';

const qrData = createSignedQR({
  privateKey: 'your-private-key',
  keyLocator: 'warehouse._dspip.example.com',
  payload: {
    sender: {
      name: 'ACME Corp',
      address: { country: 'US' }
    },
    recipient: {
      name: 'Bob Jones',
      address: { country: 'US' }
    },
    parcelId: 'ACME-2025-000123',
    timestamp: Date.now()
  }
});

5. Verify a QR Code

import { verify } from '@dspip/core';

const result = await verify(qrData);

if (result.valid) {
  console.log('Signature verified!');
  console.log('Sender:', result.payload.sender);
  console.log('Parcel ID:', result.payload.parcelId);
} else {
  console.error('Verification failed:', result.error);
}

Installation

JavaScript / TypeScript

npm install @dspip/core
# or
yarn add @dspip/core

Python

pip install dspip

Go

go get github.com/dspip/dspip-go

From Source

git clone https://github.com/MidwestCyberLLC/dspip.git
cd dspip
npm install
npm run build

Digital Envelope Model

DSPIP follows a "digital envelope" paradigm analogous to physical mail. Understanding this model is key to implementing the protocol correctly.

Envelope Exterior (Public)

Like the address on a physical envelope, the following information is encoded but not encrypted. Anyone can read this data:

  • Sender identity and address
  • Recipient identity and address
  • Parcel identifier
  • Timestamp

Signature (Authenticity Seal)

A cryptographic signature proves the envelope was created by the claimed sender and has not been tampered with. Similar to a wax seal or registered mail receipt.

Private Message (Optional)

An optional message encrypted specifically for the recipient. Only the recipient can decrypt this content, analogous to the letter inside a sealed envelope.

Design Philosophy

This model intentionally makes routing information public while protecting optional private communications. This mirrors how physical mail works and enables verification without decryption.

Protocol Flow

The DSPIP protocol consists of these steps:

  1. Payload Creation
    Sender creates a JSON payload containing sender info, recipient info, parcel ID, and timestamp.
  2. Base64 Encoding
    Payload is Base64 encoded (NOT encrypted) to ensure public readability.
  3. Signing
    Sender signs the protocol identifier, version, key locator, and encoded payload using their private key.
  4. QR Generation
    Complete data structure is serialized with pipe delimiters and encoded as a QR code.
  5. Physical Attachment
    QR code is printed and attached to the physical item.
  6. Scanning
    Any party can scan the QR code to retrieve the data.
  7. DNS Lookup
    Scanner queries DNS TXT record at the key locator to retrieve the public key.
  8. Verification
    Scanner verifies the ECDSA signature using the retrieved public key.
  9. Optional: Blockchain Recording
    If configured, custody event is recorded to blockchain for immutable audit trail.
  10. Optional: Private Message Decryption
    Recipient can decrypt optional encrypted message using their private key.

Compliance Levels

DSPIP defines three compliance levels to accommodate different security requirements:

Level 1
Basic
Consumer and small business applications
  • MUST support signature verification
  • MUST support DNS lookup
  • MAY cache DNS records
  • MAY omit private message support
Level 2
Enterprise
Business and commercial applications
  • All Level 1 requirements
  • MUST support key expiration checking
  • MUST implement rate limiting
  • SHOULD support blockchain recording
  • SHOULD support offline verification
Level 3
High Security
DoD and government applications
  • All Level 2 requirements
  • MUST use DNSSEC validation
  • MUST support HSM key storage
  • MUST record blockchain custody events
  • MUST implement anti-counterfeiting

MUST   SHOULD   MAY

Payload Structure

The payload contains publicly readable parcel information encoded as JSON:

{
  "sender": {
    "name": "string",           // Optional
    "organization": "string",   // Optional
    "address": {
      "street1": "string",      // Optional
      "street2": "string",      // Optional
      "city": "string",         // Optional
      "state": "string",        // Optional
      "postalCode": "string",   // Optional
      "country": "string"       // REQUIRED: ISO 3166-1 alpha-2
    },
    "email": "string",          // Optional
    "publicKeyId": "string"     // Optional: for recipient message encryption
  },
  "recipient": {
    // Same structure as sender
  },
  "parcelId": "string",         // REQUIRED: Unique identifier
  "timestamp": number,         // REQUIRED: Unix timestamp (ms)
  "message": "string",          // Optional: Public note
  "gs1": "string",              // Optional: GS1 identifier
  "edi": "string",              // Optional: EDI reference
  "uid": "string"               // Optional: DoD UID
}

Required Fields

Field Type Description
parcelId string Unique identifier for the parcel
timestamp number Unix timestamp in milliseconds
sender.address.country string ISO 3166-1 alpha-2 country code
recipient.address.country string ISO 3166-1 alpha-2 country code
Extensibility

Organizations MAY include additional fields for compatibility with their systems. Scanners MUST ignore unknown fields to maintain forward compatibility.

QR Serialization

QR data MUST be serialized using pipe (|) delimiters:

DSPIP|<version>|<keyLocator>|<encodedPayload>|<signature>[|<recipientMessage>]

Fields

Field Description Example
protocol Fixed string "DSPIP" DSPIP
version Semantic version 1.0
keyLocator DNS path for public key warehouse._dspip.example.com
encodedPayload Base64-encoded JSON eyJzZW5kZXIiOi...
signature Hex-encoded ECDSA (DER) 3045022100...
recipientMessage Optional encrypted message Base64 encoded

QR Code Requirements

Error Correction Level M (15% recovery) recommended
Version 40 or automatic selection
Encoding Mode Binary for optimal density
Max Capacity (Level M) 2,331 bytes
Validation Required

Implementations MUST validate that exactly 5 or 6 pipe-delimited fields are present. The pipe delimiter was chosen for its low frequency in Base64 and domain names.

DNS Records

Key Locator Format

Key locators MUST follow this pattern:

<selector>._dspip.<domain>

Where:

  • selector: Unique identifier for the key (e.g., "warehouse-a", "emp-jsmith")
  • _dspip: Fixed protocol subdomain (underscore prefix per RFC 8552)
  • domain: Organization's domain name

Examples

warehouse._dspip.example.com
emp-jsmith._dspip.example.org
device-001._dspip.example.net

DNS TXT Record Format

The DNS TXT record MUST contain semicolon-separated tag=value pairs:

v=DSPIP1; k=ec; c=secp256k1; p=<base64_public_key>; [optional tags]

Required Tags

v
string
Protocol version. MUST be "DSPIP1"
Required
k
string
Key type. MUST be "ec" (elliptic curve)
Required
c
string
Curve identifier. MUST be "secp256k1"
Required
p
string
Public key in Base64 (33 bytes compressed)
Required

Optional Tags

t
number
Creation timestamp (Unix time)
x
number
Expiration timestamp (Unix time)
n
string
Human-readable notes (percent-encoded)
eth
string
Ethereum address for blockchain integration
chain
string
Blockchain network (e.g., "polygon", "ethereum")

Complete Example

warehouse._dspip.example.com. IN TXT "v=DSPIP1; k=ec; c=secp256k1;
  p=AzmjYBMwFZfa70H75ZOgLMUT0LVVJ+wt8QUOLo/0nIXC; t=1703548800;
  eth=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0; chain=polygon"

Key Generation

DSPIP uses the secp256k1 elliptic curve, the same curve used by Bitcoin and Ethereum.

Specifications

Curve secp256k1 (SEC 2)
Private Key Size 256 bits (32 bytes)
Public Key Size 264 bits (33 bytes compressed)
Coordinate System Compressed point format

Generation Process

  1. Generate cryptographically secure random 32-byte private key
  2. Calculate public key point on secp256k1 curve
  3. Compress public key to 33-byte format
  4. Optionally derive Ethereum address for blockchain integration

Code Example

import { generateKeyPair, deriveEthAddress } from '@dspip/core';

// Generate new key pair
const { privateKey, publicKey, publicKeyBase64 } = generateKeyPair();

// Optional: derive Ethereum address
const ethAddress = deriveEthAddress(publicKey);

console.log('Private Key:', privateKey);
console.log('Public Key (base64):', publicKeyBase64);
console.log('Ethereum Address:', ethAddress);
Key Compatibility

The same private key can be used for DSPIP signatures, Ethereum/Polygon transactions, and Bitcoin transactions. No separate key management needed!

Signature Creation

Signatures are created using ECDSA over the secp256k1 curve:

  1. Construct Signable Content
    Concatenate with pipe delimiters: protocol|version|keyLocator|encodedPayload
  2. Calculate Hash
    Compute SHA-256 hash of the signable content
  3. Sign
    Sign the hash using ECDSA with the private key
  4. Encode
    Encode signature in DER format, then convert to hexadecimal string

What Gets Signed

The signature covers: protocol identifier, version, key locator, and encoded payload. It does NOT cover the signature field itself or the optional recipient message.

const signableContent = `${protocol}|${version}|${keyLocator}|${encodedPayload}`;
const hash = sha256(signableContent);
const signature = ecdsaSign(hash, privateKey);
const signatureHex = derEncode(signature).toString('hex');

Verification

Verification Algorithm

  1. Parse QR Data
    Split by pipe delimiter. Validate exactly 5 or 6 fields. Extract protocol, version, keyLocator, encodedPayload, signature, [recipientMessage].
  2. Validate Protocol
    Protocol MUST equal "DSPIP". Version MUST be compatible (same major version).
  3. Decode Payload
    Base64 decode encodedPayload. Parse JSON. Validate required fields.
  4. DNS Lookup
    Check local cache for keyLocator. If not cached, query DNS TXT record. Parse tags and extract public key.
  5. Verify Signature
    Reconstruct signable content. Verify ECDSA signature with public key. Check key expiration if present.
  6. Optional: Blockchain Recording
    If eth tag present in DNS record, record custody event to blockchain.

Code Example

import { verify } from '@dspip/core';

const result = await verify(qrData, {
  cacheEnabled: true,
  dnssecRequired: false,  // true for Level 3
  recordToBlockchain: false
});

if (result.valid) {
  console.log('Valid!');
  console.log('Payload:', result.payload);
  console.log('Signer:', result.keyLocator);
} else {
  console.error('Error:', result.errorCode, result.errorMessage);
}
Expired Keys

Implementations MUST accept signatures from expired keys for parcels created before the expiration date. Check the payload timestamp against key expiration.

Blockchain Integration

DSPIP supports optional blockchain recording for immutable custody tracking. Since DSPIP uses secp256k1 keys, the same keys can sign both QR codes and blockchain transactions.

Enabling Blockchain

Add blockchain information to your DNS TXT record:

v=DSPIP1; k=ec; c=secp256k1; p=...;
  eth=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0;
  chain=polygon

Custody Event Structure

{
  "parcelId": "sha256(DSPIP-parcelId)",  // Hashed for privacy
  "custodian": "ethereum_address",
  "timestamp": "block.timestamp",
  "location": "geohash_or_facility_id",
  "previousCustodyHash": "bytes32"
}
Privacy Warning

Only hashed parcel IDs should be stored on public blockchains. Personal information MUST NOT be recorded on immutable ledgers.

Offline Verification

For environments without network connectivity, DSPIP supports offline verification using cached DNS record bundles.

Cache Bundle Format

{
  "version": "1.0",
  "generated": 1703548800,
  "expires": 1704153600,
  "records": {
    "warehouse._dspip.example.com": "v=DSPIP1; k=ec; ...",
    "emp-jsmith._dspip.example.com": "v=DSPIP1; k=ec; ..."
  },
  "signature": "<bundle_signature>"
}

Requirements

  • Cache bundles MUST be updated at least weekly
  • Bundles MUST be signed by organization's root key
  • Critical revocations SHOULD use out-of-band distribution

DNS Sharding

Large organizations with thousands of keys MAY implement DNS sharding to distribute records across multiple subdomains.

Root Discovery Record

_dspip.example.org. IN TXT "v=DSPIP1; mode=sharded;
  shards=_dspip01,_dspip02,_dspip03,_dspip04; count=4"

Shard Distribution Algorithm

  1. Calculate SHA-256 hash of selector
  2. Take hash modulo shard count
  3. Map to shard subdomain with zero-padded index

Lookup Procedure

  1. Query _dspip.<domain> for root record
  2. If mode=direct, key is at <selector>._dspip.<domain>
  3. If mode=sharded:
    1. Calculate shard from selector hash
    2. Query <selector>.<shard>.<domain>
  4. If not found in calculated shard, MAY query other shards

Error Handling

Error Codes

INVALID_PROTOCOL
Protocol field is not "DSPIP"
Fatal
PARSE_ERROR
Cannot parse QR structure (wrong number of fields)
Fatal
INVALID_PAYLOAD
Cannot decode or parse payload JSON
Fatal
MISSING_REQUIRED_FIELD
Required payload field is missing
Fatal
DNS_LOOKUP_FAILED
Cannot resolve key locator
Fatal
INVALID_DNS_RECORD
DNS record doesn't contain valid DSPIP key
Fatal
SIGNATURE_INVALID
Signature verification failed
Fatal
KEY_EXPIRED
Key has passed its expiration date
Warning
KEY_REVOKED
Key has been revoked
Fatal

Test Vectors

Warning: Test Keys Only

The private key below is PUBLIC and MUST NOT be used for any production purpose. It is provided solely for verifying implementation correctness.

Key Pair

Private Key (hex)

e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35

Public Key Compressed (hex)

0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2

Public Key Base64 (for DNS)

AzmjYBMwFZfa70H75ZOgLMUT0LVVJ+wt8QUOLo/0nIXC

Sample Payload

{
  "sender": {
    "name": "Alice Smith",
    "organization": "ACME Corp",
    "address": {
      "street1": "123 Main Street",
      "street2": "Suite 100",
      "city": "New York",
      "state": "NY",
      "postalCode": "10001",
      "country": "US"
    }
  },
  "recipient": {
    "name": "Bob Jones",
    "address": {
      "street1": "456 Market Street",
      "street2": "",
      "city": "Los Angeles",
      "state": "CA",
      "postalCode": "90001",
      "country": "US"
    }
  },
  "parcelId": "ACME-2025-000123",
  "timestamp": 1703548800000
}

DNS TXT Record

warehouse._dspip.example.com. IN TXT "v=DSPIP1; k=ec; c=secp256k1;
  p=AzmjYBMwFZfa70H75ZOgLMUT0LVVJ+wt8QUOLo/0nIXC; t=1703548800;
  n=ACME%20Warehouse%20A"

Complete QR Data

DSPIP|1.0|warehouse._dspip.example.com|eyJzZW5kZXIiOnsibmFtZSI6IkFsaWNlIFNtaXRoIiwib3JnYW5pemF0aW9uIjoiQUNNRSBDb3JwIiwiYWRkcmVzcyI6eyJzdHJlZXQxIjoiMTIzIE1haW4gU3RyZWV0Iiwic3RyZWV0MiI6IlN1aXRlIDEwMCIsImNpdHkiOiJOZXcgWW9yayIsInN0YXRlIjoiTlkiLCJwb3N0YWxDb2RlIjoiMTAwMDEiLCJjb3VudHJ5IjoiVVMifX0sInJlY2lwaWVudCI6eyJuYW1lIjoiQm9iIEpvbmVzIiwiYWRkcmVzcyI6eyJzdHJlZXQxIjoiNDU2IE1hcmtldCBTdHJlZXQiLCJzdHJlZXQyIjoiIiwiY2l0eSI6IkxvcyBBbmdlbGVzIiwic3RhdGUiOiJDQSIsInBvc3RhbENvZGUiOiI5MDAwMSIsImNvdW50cnkiOiJVUyJ9fSwicGFyY2VsSWQiOiJBQ01FLTIwMjUtMDAwMTIzIiwidGltZXN0YW1wIjoxNzAzNTQ4ODAwMDAwfQ==|304502202b76aa5c68997c1a563675860b90eaa23bd93b377d2e303d4588472ab0a9645f0221008cca58b83c1dfa558c01d4bc1e4266fc87afe41bab92cbd11e13b418b9bbc83c

Live Test DNS Record

Query this record to test your implementation:

dig TXT test._dspip.dspip.io

Implementation Checklist

Use this checklist to verify your implementation is complete:

Parsing

  • Parse QR data with exactly 5 or 6 pipe-delimited fields
  • Validate protocol field equals "DSPIP"
  • Validate version compatibility (same major version)

Payload Handling

  • Base64 decode the payload
  • Parse JSON payload
  • Validate required fields: parcelId, timestamp, country codes
  • Ignore unknown fields for forward compatibility

DNS

  • Query DNS TXT record at key locator
  • Parse semicolon-delimited tags
  • Validate v=DSPIP1, k=ec, c=secp256k1
  • Base64 decode public key (33 bytes)
  • Implement caching (optional for Level 1, required for Level 2+)

Cryptography

  • Reconstruct signable content: protocol|version|keyLocator|encodedPayload
  • SHA-256 hash the signable content
  • Hex decode the signature
  • Verify ECDSA signature using secp256k1
  • Check key expiration if x tag present

Validation

  • Verify test vectors produce expected results
  • Test against live DNS record at test._dspip.dspip.io
  • Handle all error conditions appropriately

Resources

Official Links

SDKs

  • npm: @dspip/core
  • pip: dspip
  • Go: github.com/dspip/dspip-go

Standards References

Community