Loading...
private.me Docs
Get AdSplit
PRIVATE.ME · Technical White Paper

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.

v0.1.0 24 tests passing Zero npm deps Information-theoretic HMAC-verified
Section 01

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.

Section 02

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.

Section 03

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:

Configuration
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.

Conversion event
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.

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

Reconstruction
// 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.

Section 04

Use Cases

📊
Advertising
Privacy-First Attribution
Publishers and advertisers measure conversion performance without exposing user data to a centralized data broker.
2-of-3 threshold
🔍
Compliance
GDPR Data Minimization
Each attribution node receives only a meaningless share, proving mathematical compliance with Article 5 data minimization requirements.
Auditable
🏦
Financial Services
MiFID II Transaction Monitoring
Track financial campaign conversions across regions while splitting data across jurisdictions for compliance.
Multi-region
⚖️
Government
Classified Campaign Measurement
Measure recruiting or awareness campaign effectiveness across agency, contractor, and auditor without any party seeing the full dataset.
Zero Trust
Section 05

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.

Architecture

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.

Section 06

API Surface

Public Functions

splitConversion(event, config) → Promise<Result<ConversionSplitResult, AdSplitError>>
Validates config, pads the conversion event data, computes HMAC-SHA256, and splits via XorIDA. Returns shares ready for distribution.
reconstructConversion(shares) → Promise<Result<Uint8Array, AdSplitError>>
Validates share consistency, verifies HMAC integrity, reconstructs via XorIDA, unpads, and returns the original conversion data. Fails closed if HMAC fails.
validateAdConfig(config) → Result<true, AdSplitError>
Validates a configuration object before use. Checks that nodes ≥ 2, threshold ≥ 2, and threshold ≤ nodes. Returns error with actionable message if invalid.

Type Signatures

Core types
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; };
Section 07

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.

Guarantee

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.

Section 08

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.

Section 09

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:

Error handling
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
Section 10

Performance

AdSplit inherits performance characteristics from XorIDA, which is sub-millisecond for typical ad conversion payloads (200 bytes – 1 KB):

< 1ms
Split (256B payload)
< 1ms
Reconstruct (256B)
< 2ms
HMAC verify
0 network
Share transport

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.

Section 11

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.

Section 12

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.

Post-Quantum Guarantee

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.

Advanced Topics

Deep Dives

For architects and cryptographers: detailed specifications, threat modeling, and implementation notes.

Advanced 01

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.

Advanced 02

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.

Advanced 03

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.

Advanced 04

Codebase Statistics

24
Total Tests
2
Main Modules
6
Error Codes
0
Runtime Dependencies

File Structure

The package is minimal:

  • src/types.ts — Type definitions
  • src/index.ts — Public API barrel export
  • src/conversion-splitter.ts — Splitting logic
  • src/conversion-reconstructor.ts — Reconstruction logic
  • src/errors.ts — Error definitions
  • src/__tests__/ — Test files
  • docs/threat-model.md — Threat model document
  • docs/failure-modes.md — Failure modes analysis

Test Coverage

24 tests organized into two test files:

  • conversion-splitter.test.ts — Split operations, config validation, edge cases
  • abuse.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

📦

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
Get Started →
🏢

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
Request Quote →

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.

Contact sales for assessment and pricing →