AdSplit: Privacy-Preserving Ad Attribution
Digital advertising requires attribution, but centralizing conversion data invites surveillance and breach. AdSplit uses XorIDA threshold secret sharing to split conversion event data across independent attribution nodes so that no single node can see the complete record. Accurate attribution measurement requires a configurable threshold of cooperating nodes, enabling privacy-safe compliance with regulatory data minimization rules.
Executive Summary
AdSplit enables accurate ad conversion attribution while mathematically preventing any single attribution node from accessing complete user conversion data.
Every ad conversion event (purchase, signup, click) carries sensitive user signals: identity, monetary value, campaign association, behavioral context. Current attribution models concentrate this data at a single vendor, creating a massive surveillance target. AdSplit decouples knowledge from capability: the publisher knows the click happened, the advertiser knows the conversion happened, and an auditor can verify both. But no single party can reconstruct the complete conversion record without threshold cooperation.
Two functions cover the entire API: splitConversion() takes a conversion event and configuration, splits the data via XorIDA across attribution nodes, and returns shares. reconstructConversion() takes a threshold of shares, verifies HMAC integrity, reconstructs the original data, and returns the plaintext — or fails closed with a detailed error code if anything is tampered.
The security guarantee is information-theoretic: any subset of shares below the threshold reveals zero bits of information about the original conversion event. This is not computational hardness (which quantum computers can break) but mathematical impossibility.
Zero npm runtime dependencies. Zero external API calls. Runs on Node.js 20+, Deno, Bun, Tauri, any environment with Web Crypto API. Test coverage: 24 tests across 2 main flows (split + reconstruct) with comprehensive abuse cases.
The Problem: Attribution Surveillance
Attribution is necessary. Privacy is necessary. Centralizing attribution data violates both.
Why Attribution Matters
Advertisers need to know if a campaign works. Publishers need to know which placements drive revenue. Auditors need to verify the claims both sides make. This requires tracking a conversion event from the moment a user sees an ad to the moment they complete a transaction (or signup, or click). This attribution data is essential to the digital economy.
The Centralization Problem
Today's ad-tech ecosystem solves this with middlemen: DSPs, ad exchanges, data brokers, analytics platforms. Each one collects conversion events into a central database. Each one becomes a honeypot. A breach exposes not just one advertiser's data or one publisher's data — it exposes a complete view of user behavior across the entire open web.
This also creates perverse incentives: middlemen profit from data hoarding, making the data more valuable the more concentrated it is. They have every incentive to resist sharing, resist auditing, resist privacy protection.
Regulatory Pressure
GDPR (Article 5) mandates data minimization: collect only what you need, and no more. CCPA gives users the right to know what data is collected. Ad attribution, historically, has violated both principles by default.
Market Demand
Privacy-first browsers, first-party cookie deprecation, iOS App Tracking Transparency, and Zero Trust architectures are all pushing the market toward decentralized attribution. Advertisers and publishers increasingly need a solution that gives them attribution without surveillance.
How AdSplit Works
The Core Idea: Threshold Secret Sharing
Instead of sending a conversion event to a single attribution node, AdSplit splits it across multiple independent nodes using XorIDA threshold secret sharing.
Think of a conversion event as a secret message. XorIDA creates N shares such that any K shares can reconstruct the message, but any K-1 shares reveal zero information. This is information-theoretic security: not a computational assumption, but a mathematical fact.
Step-by-Step Flow
1. Configure Attribution Nodes
Define which nodes will participate (e.g., publisher, advertiser, auditor) and the reconstruction threshold. For example:
const config: AdConfig = { nodes: [ { id: 'pub', name: 'Publisher', role: 'publisher' }, { id: 'adv', name: 'Advertiser', role: 'advertiser' }, { id: 'audit', name: 'Auditor', role: 'auditor' }, ], threshold: 2, // Any 2 of 3 shares can reconstruct };
2. Create a Conversion Event
Package the conversion data: user identifier, campaign ID, purchase amount, event type, timestamp, and raw payload.
const event: ConversionEvent = { eventId: 'EVT-2026-0042', campaignId: 'CAMP-Q1-2026', userId: 'pseudo-user-abc', // Pseudonymous eventType: 'purchase', value: '49.99', // String to avoid float precision loss currency: 'USD', timestamp: Date.now(), data: new Uint8Array([/* raw bytes */]), };
3. Split the Conversion
Call splitConversion() to produce shares.
const result = await splitConversion(event, config); // result.shares contains 3 ConversionShare objects // Each node receives its own share // No share alone reveals anything about the conversion
4. Transport Shares Independently
Each share is sent to its assigned node independently. The shares can travel over insecure channels, be stored in separate systems, or even be archived. A single share in isolation is just random noise.
5. Reconstruct When Needed
When you need to measure attribution, collect threshold shares and call reconstructConversion().
// Collect threshold shares from nodes const shares = await collectSharesFromNodes(threshold); // Reconstruct const reconstructed = await reconstructConversion(shares); // HMAC verified automatically, fails closed if tampered if (reconstructed.ok) { const originalData = reconstructed.value; // Process the conversion data }
HMAC Integrity Protection
Before splitting, AdSplit computes an HMAC-SHA256 over the padded conversion data. This HMAC is stored alongside each share. During reconstruction, HMAC verification is mandatory before the plaintext is returned. If any share is tampered, the HMAC fails and reconstruction is rejected (fail closed). This prevents fraud where a malicious node tries to modify its share to inflate conversion values.
Event ID Validation
Each share carries an eventId. During reconstruction, all shares are validated to have the same event ID. This prevents cross-event share mixing attacks where an attacker tries to blend shares from different conversion events to create false records.
Use Cases
Architecture
Module Structure
AdSplit is organized into two main modules:
| Module | Purpose | Key Functions |
|---|---|---|
| conversion-splitter.ts | Validates config and splits conversion events | splitConversion(), validateAdConfig() |
| conversion-reconstructor.ts | Reconstructs and verifies HMAC | reconstructConversion() |
Data Flow Diagram
The flow is simple: validate → split → distribute → collect → verify → reconstruct.
Validation Phase: Config is validated for node count ≥ 2, threshold ≥ 2, threshold ≤ nodes.
Splitting Phase: Event data is padded (PKCS#7), HMAC-SHA256 is computed, XorIDA splitting produces N shares.
Distribution Phase: Each share is tagged with its node ID and transported independently.
Collection Phase: Threshold shares are collected (could take hours, days, or weeks).
Verification Phase: Event IDs are checked for consistency, HMAC is verified before reconstruction.
Reconstruction Phase: XorIDA reconstruction produces plaintext, which is unpadded and returned.
API Surface
Public Functions
Type Signatures
interface ConversionEvent { eventId: string; // Unique event identifier campaignId: string; // Campaign this event belongs to userId: string; // Pseudonymous user identifier eventType: string; // 'purchase', 'signup', 'click', etc. value: string; // Monetary value as string (decimal) currency: string; // ISO 4217 code (USD, EUR, etc.) timestamp: number; // Unix ms timestamp data: Uint8Array; // Raw conversion payload } interface AdConfig { nodes: AttributionNode[]; // List of attribution nodes threshold: number; // K-of-N threshold } interface ConversionShare { eventId: string; nodeId: string; index: number; // 0-based share index total: number; // Total number of shares threshold: number; // Reconstruction threshold data: string; // Base64-encoded share data hmac: string; // Base64 key:signature originalSize: number; // Size before padding } type AdSplitError = | { code: 'INVALID_CONFIG'; message: string; } | { code: 'SPLIT_FAILED'; message: string; } | { code: 'HMAC_FAILED'; message: string; } | { code: 'RECONSTRUCT_FAILED'; message: string; } | { code: 'INSUFFICIENT_SHARES'; message: string; } | { code: 'EVENT_ID_MISMATCH'; message: string; };
Security Model
Information-Theoretic Security
AdSplit's core security guarantee is information-theoretic: any subset of shares below the threshold mathematically reveals zero information about the plaintext conversion event. This is not a computational assumption (no "assuming AES is hard to break") but a mathematical fact.
For a 2-of-3 threshold configuration, either of the 3 shares alone is perfectly random and independent of the plaintext. An attacker who obtains share 1 learns nothing about the conversion event. An attacker who obtains shares 1 and 2 can reconstruct the plaintext, but a single share tells them nothing.
HMAC Integrity
Before splitting, an HMAC-SHA256 is computed over the padded conversion data. This HMAC is stored in each share. During reconstruction, HMAC verification is mandatory and happens before the plaintext is returned. If the HMAC fails, reconstruction is rejected (fail closed) and an error is returned.
This prevents fraud where a malicious attribution node modifies its share to inflate conversion values or alter campaign attribution metrics.
No Math.random()
All randomness is generated via crypto.getRandomValues(). The API is called once during splitting to generate random bytes for the XorIDA operation. This ensures cryptographic-grade entropy, not predictable pseudorandom values.
Event ID Validation
Each share carries an eventId. During reconstruction, all provided shares are checked to have the same event ID. Mixing shares from different events produces an EVENT_ID_MISMATCH error and reconstruction is rejected.
No Persistent Plaintext
The service never stores the original conversion event plaintext. After splitting, only shares and HMAC metadata exist. The plaintext lives only in memory during splitting and reconstruction, then is discarded.
String-Typed Monetary Values
The value field in ConversionEvent is a string, not a number. This avoids floating-point precision loss. A purchase amount of $49.99 stays exactly "$49.99" throughout splitting, transport, and reconstruction.
Threat Mitigations
Threat 1: Single Attribution Node Data Harvesting
Threat: A malicious attribution node tries to extract user signals from its share (user ID, purchase amount, campaign).
Mitigation: XorIDA information-theoretic security ensures that a single share is perfectly random and carries zero information about the original conversion event. The node receives opaque bytes.
Residual Risk: None, by design. Single-node data access is mathematically impossible.
Threat 2: Conversion Data Manipulation for Fraud
Threat: A malicious attribution node modifies its share to inflate conversion values or alter attribution metrics.
Mitigation: HMAC-SHA256 computed over padded data before splitting. Reconstruction verifies HMAC before returning plaintext. Tampered shares produce HMAC_FAILED error.
Detection: Monitoring for elevated HMAC_FAILED error rates flags node misbehavior.
Threat 3: Cross-Event Share Mixing (Attribution Poisoning)
Threat: An attacker mixes shares from different conversion events to create false attribution records.
Mitigation: Each share carries an eventId validated during reconstruction. Shares with different event IDs produce EVENT_ID_MISMATCH error.
Threat 4: Regulatory Data Access Request
Threat: A regulator requests conversion data from a single attribution node during audit or investigation.
Mitigation: The node can only produce its shares, which are information-theoretically meaningless without threshold cooperation. This provides mathematical evidence of data minimization compliance under GDPR Article 5 and CCPA.
Threat 5: Replay of Historical Conversion Data
Threat: An attacker replays old conversion shares to create duplicate attribution records and inflate metrics.
Mitigation: Each split produces a unique eventHash (SHA-256). Applications should track processed event hashes and reject replays. The eventId and timestamp fields enable deduplication at the application layer.
Note: AdSplit itself does not maintain a replay log; this is an application-layer responsibility.
Error Handling
Error Codes
AdSplit uses discriminated union error types for precise error handling:
| Code | When | Recovery |
|---|---|---|
| INVALID_CONFIG | Nodes < 2, threshold < 2, threshold > nodes | Correct the configuration and retry |
| SPLIT_FAILED | XorIDA split error or empty data | Verify event data is non-empty; check crypto library |
| HMAC_FAILED | HMAC verification failed during reconstruction | Discard shares; collect fresh shares from nodes |
| RECONSTRUCT_FAILED | XorIDA reconstruction or unpadding failed | Verify shares are uncorrupted; re-collect if needed |
| INSUFFICIENT_SHARES | Fewer shares than threshold provided | Collect additional shares from remaining nodes |
| EVENT_ID_MISMATCH | Shares have different eventIds | Separate shares by event ID; retry with matching shares |
Result<T, E> Pattern
All functions return a Result type: { ok: true, value: T } on success or { ok: false, error: E } on failure. This eliminates unexpected exceptions:
const splitResult = await splitConversion(event, config); if (!splitResult.ok) { console.error('Split failed:', splitResult.error.code, splitResult.error.message); return; } const { shares } = splitResult.value; // Process shares
Performance
AdSplit inherits performance characteristics from XorIDA, which is sub-millisecond for typical ad conversion payloads (200 bytes – 1 KB):
Complexity Analysis
XorIDA splitting: O(n·m) where n is the number of nodes and m is the payload size.
XorIDA reconstruction: O(k·m) where k is the threshold and m is the payload size.
HMAC-SHA256: O(m) where m is the payload size.
For a 2-of-3 configuration with a 512-byte conversion event, the entire split-and-reconstruct cycle takes under 3 milliseconds end-to-end on modern hardware.
Honest Limitations
Threshold Compromise
If an attacker compromises K (threshold) attribution nodes, they can reconstruct the plaintext conversion events. The system is designed to tolerate K-1 compromised nodes but not K. For a 2-of-3 configuration, the attacker needs 2 of the 3 nodes.
Mitigation: Use a higher threshold (e.g., 3-of-5) if you're concerned about nation-state adversaries. Monitor node availability closely. If a node goes offline, shares cannot be reconstructed until it returns.
No Replay Protection at the Library Level
AdSplit computes an eventHash but does not maintain a replay log. Applications must track event hashes and reject duplicates themselves. This is intentional: AdSplit is a splitting library, not a full attribution service.
No End-to-End Encryption of Shares
Shares are base64-encoded but not encrypted in transit. Applications should wrap shares in TLS/HTTPS or encrypt them with the node's public key. AdSplit focuses on privacy at the data layer (threshold sharing); transport security is application-layer responsibility.
Configuration is Static
Once a conversion event is split with a 2-of-3 configuration, it cannot be re-split with a different configuration (e.g., 2-of-4 or 3-of-4). This would require re-splitting from plaintext, which violates the assumption that plaintext is never persisted.
No Key Rotation
Shares are tagged with node IDs, not key IDs. If a node rotates its identity, old shares cannot be attributed to the new identity without application-layer mapping.
Post-Quantum Security
Quantum-Safe by Design
AdSplit's information-theoretic security is unconditionally quantum-safe. The XorIDA splitting algorithm relies only on linear algebra over GF(2), not on any computational hardness assumption. Quantum computers cannot break it because there is nothing computational to break — it is a mathematical impossibility for a single share to reveal the plaintext.
Even a quantum computer with arbitrary computing power cannot reconstruct conversion data from fewer than K shares, because fewer than K shares are literally insufficient bits of information to specify the plaintext.
Transport-Layer Post-Quantum Security
When shares are exchanged via Xlink or other agents using the PRIVATE.ME platform, the transport envelope adds hybrid post-quantum cryptography:
Key Exchange: X25519 + ML-KEM-768 (FIPS 203) — always enabled.
Signatures: Ed25519 + ML-DSA-65 (FIPS 204) — opt-in via postQuantumSig: true.
Applications integrating AdSplit into Xlink agents should enable post-quantum signatures for full post-quantum protection across all three cryptographic layers: (1) payload sharing, (2) envelope encryption, (3) envelope signature.
Deep Dives
For architects and cryptographers: detailed specifications, threat modeling, and implementation notes.
Failure Modes
FM-1: HMAC Verification Failure
Error Code: HMAC_FAILED
Cause: Attribution node share data was modified, HMAC key or signature was corrupted, or shares from different events were mixed.
Detection: reconstructConversion() returns HMAC_FAILED before data consumption.
Recovery: Discard the reconstruction attempt. Request fresh shares from attribution nodes. Investigate share transport integrity and node storage integrity.
FM-2: Insufficient Attribution Node Shares
Error Code: INSUFFICIENT_SHARES
Cause: Fewer than K (threshold) node shares were provided. A node may be offline, shares were lost in transport, or storage failure occurred.
Detection: Validation in reconstructConversion() checks share count against threshold.
Recovery: Collect additional shares from remaining nodes. If nodes are permanently unavailable, that conversion event cannot be attributed (data loss is acceptable for privacy, as it prevents profiling).
FM-3: Event ID Mismatch
Error Code: EVENT_ID_MISMATCH
Cause: Shares from different conversion events were provided in the same reconstruction call.
Detection: Validation in reconstructConversion() checks that all shares have the same eventId.
Recovery: Separate shares by event ID and retry with shares belonging to the same conversion event.
FM-4: Invalid Configuration
Error Code: INVALID_CONFIG
Cause: Fewer than 2 attribution nodes, threshold < 2, or threshold > node count.
Detection: validateAdConfig() and splitConversion() validate before cryptographic operations.
Recovery: Correct the configuration. Ensure nodes ≥ 2, threshold ≥ 2, threshold ≤ node count.
FM-5: Empty Conversion Data
Error Code: SPLIT_FAILED
Cause: The conversion event's data field is empty (zero-length Uint8Array).
Detection: splitConversion() checks for empty data before splitting.
Recovery: Ensure the conversion event is fully populated before splitting. Verify data capture from the conversion pipeline.
FM-6: XorIDA Reconstruction Failure
Error Code: RECONSTRUCT_FAILED
Cause: XorIDA reconstruction succeeded but unpadding failed, or internal matrix operations produced invalid output.
Detection: reconstructConversion() returns RECONSTRUCT_FAILED.
Recovery: Verify shares are uncorrupted and belong to the same event. Re-collect shares if necessary. Report persistent failures to the platform team.
Transport Layer
Share Distribution Patterns
Shares can be distributed via any transport: HTTPS APIs, message queues, object storage, IPFS, broadcast channels, etc. The shares themselves do not need encryption because a single share is information-theoretically secure (reveals nothing).
Transport Security Recommendations
- Use TLS for all network transport (HTTPS, WSS, etc.)
- Verify share integrity via a second channel (e.g., signature from the splitting service)
- Consider encrypting shares with the recipient node's public key as a defense-in-depth measure
- Do NOT store plaintext conversion data alongside shares in the same system
- Do NOT log share contents (only log error codes and share IDs)
Asynchronous Collection
Shares can be collected over any timeframe: immediately, days later, or weeks later. The time delay does not affect security. The timestamp field in the original event helps with ordering and deduplication.
HMAC Verification Details
Algorithm
AdSplit uses HMAC-SHA256 for integrity protection. The HMAC is computed over the padded conversion data before splitting.
When HMAC is Verified
HMAC verification happens after XorIDA reconstruction but before unpadding. This is critical: the reconstructed data is checked for integrity before being returned to the application.
Fail-closed behavior: if HMAC fails, the plaintext is never returned, and an error is thrown.
HMAC Storage
The HMAC is stored in each ConversionShare as a base64-encoded string: base64(key) + ':' + base64(signature). All shares in a threshold set carry the same HMAC metadata (same key, same signature) because they all came from splitting the same padded data.
HMAC Size
The HMAC-SHA256 signature is 32 bytes. The HMAC key is 32 bytes. Together, they are 64 bytes in binary, or about 86 characters when base64-encoded.
Codebase Statistics
File Structure
The package is minimal:
src/types.ts— Type definitionssrc/index.ts— Public API barrel exportsrc/conversion-splitter.ts— Splitting logicsrc/conversion-reconstructor.ts— Reconstruction logicsrc/errors.ts— Error definitionssrc/__tests__/— Test filesdocs/threat-model.md— Threat model documentdocs/failure-modes.md— Failure modes analysis
Test Coverage
24 tests organized into two test files:
conversion-splitter.test.ts— Split operations, config validation, edge casesabuse.test.ts— Fraud attempts, tampered shares, replay attacks
Lines of Code
Approximately 400 lines of TypeScript across the core modules (excluding tests and comments).
Deployment Options
SaaS Recommended
Fully managed infrastructure. Call our REST API, we handle scaling, updates, and operations.
- Zero infrastructure setup
- Automatic updates
- 99.9% uptime SLA
- Enterprise SLA available
SDK Integration
Embed directly in your application. Runs in your codebase with full programmatic control.
npm install @private.me/adsplit- TypeScript/JavaScript SDK
- Full source access
- Enterprise support available
On-Premise Upon Request
Enterprise CLI for compliance, air-gap, or data residency requirements.
- Complete data sovereignty
- Air-gap capable deployment
- Custom SLA + dedicated support
- Professional services included
Enterprise On-Premise Deployment
While AdSplit is primarily delivered as SaaS or SDK, we build dedicated on-premise infrastructure for customers with:
- Regulatory mandates — HIPAA, SOX, FedRAMP, CMMC requiring self-hosted processing
- Air-gapped environments — SCIF, classified networks, offline operations
- Data residency requirements — EU GDPR, China data laws, government mandates
- Custom integration needs — Embed in proprietary platforms, specialized workflows
Includes: Enterprise CLI, Docker/Kubernetes orchestration, RBAC, audit logging, and dedicated support.