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.
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.
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:
-
Payload Creation
Sender creates a JSON payload containing sender info, recipient info, parcel ID, and timestamp. -
Base64 Encoding
Payload is Base64 encoded (NOT encrypted) to ensure public readability. -
Signing
Sender signs the protocol identifier, version, key locator, and encoded payload using their private key. -
QR Generation
Complete data structure is serialized with pipe delimiters and encoded as a QR code. -
Physical Attachment
QR code is printed and attached to the physical item. -
Scanning
Any party can scan the QR code to retrieve the data. -
DNS Lookup
Scanner queries DNS TXT record at the key locator to retrieve the public key. -
Verification
Scanner verifies the ECDSA signature using the retrieved public key. -
Optional: Blockchain Recording
If configured, custody event is recorded to blockchain for immutable audit trail. -
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:
- MUST support signature verification
- MUST support DNS lookup
- MAY cache DNS records
- MAY omit private message support
- All Level 1 requirements
- MUST support key expiration checking
- MUST implement rate limiting
- SHOULD support blockchain recording
- SHOULD support offline verification
- 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 |
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 |
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
Optional Tags
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
- Generate cryptographically secure random 32-byte private key
- Calculate public key point on secp256k1 curve
- Compress public key to 33-byte format
- 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);
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:
-
Construct Signable Content
Concatenate with pipe delimiters:protocol|version|keyLocator|encodedPayload -
Calculate Hash
Compute SHA-256 hash of the signable content -
Sign
Sign the hash using ECDSA with the private key -
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
-
Parse QR Data
Split by pipe delimiter. Validate exactly 5 or 6 fields. Extract protocol, version, keyLocator, encodedPayload, signature, [recipientMessage]. -
Validate Protocol
Protocol MUST equal "DSPIP". Version MUST be compatible (same major version). -
Decode Payload
Base64 decode encodedPayload. Parse JSON. Validate required fields. -
DNS Lookup
Check local cache for keyLocator. If not cached, query DNS TXT record. Parse tags and extract public key. -
Verify Signature
Reconstruct signable content. Verify ECDSA signature with public key. Check key expiration if present. -
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);
}
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"
}
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
- Calculate SHA-256 hash of selector
- Take hash modulo shard count
- Map to shard subdomain with zero-padded index
Lookup Procedure
- Query
_dspip.<domain>for root record - If
mode=direct, key is at<selector>._dspip.<domain> - If
mode=sharded:- Calculate shard from selector hash
- Query
<selector>.<shard>.<domain>
- If not found in calculated shard, MAY query other shards
Error Handling
Error Codes
Test Vectors
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
- Protocol Website - dspip.io
- Developer Portal - dspip.dev
- GitHub Repository - Reference implementation
SDKs
- npm:
@dspip/core - pip:
dspip - Go:
github.com/dspip/dspip-go
Standards References
- RFC 6376 - DKIM Signatures
- RFC 8552 - Underscored DNS Naming
- SEC 2 - secp256k1 Curve Parameters
- ISO 18004 - QR Code Specification
Community
- Issue Tracker
- Discussions
- Security Reports - security@dspip.io
- General Contact - contact@dspip.io