ff-dp1-js
v1.0.0
Published
Portable DP-1 playlist and Feral File channel parsers, validators, and Ed25519 signer (browser + Node) using Zod & noble-ed25519
Maintainers
Readme
dp1-js
A lightweight JavaScript SDK for parsing, validating, and signing DP-1 playlists and Feral File channels in both Node.js and browser environments.
Overview
dp1-js provides the foundation for DP-1 tooling and FF1 apps to verify playlist and channel structure and optionally sign them with Ed25519 private keys. It creates detached signatures compatible with DP-1's signatures[] evolution, enabling secure and verifiable digital display protocols.
Features
- Parse & Validate - Parse JSON and validate DP-1 playlist and channel structure with detailed error reporting
- Sign & Verify - Create and verify Ed25519 signatures using RFC 8785 JSON canonicalization
- Universal - Works in both Node.js (22+) and modern browsers
- Type-Safe - Full TypeScript support with comprehensive type definitions
- Standards Compliant - Implements DP-1 specification with RFC 8785 canonicalization
- Feral File Channels - Extended support for Feral File channel format with additional metadata
Installation
npm install dp1-jsOr use the library directly in the browser via CDN:
Using ES Modules (Modern Browsers):
<script type="module">
import {
parseDP1Playlist,
parseChannel,
signDP1Playlist,
verifyPlaylistSignature,
signChannel,
verifyChannelSignature,
} from 'https://cdn.jsdelivr.net/npm/dp1-js/dist/index.js';
// Use the functions
const playlistResult = parseDP1Playlist(playlistData);
const channelResult = parseChannel(channelData);
</script>Quick Start
Parsing & Validating a Playlist
import { parseDP1Playlist } from 'dp1-js';
// Parse and validate playlist JSON
const result = parseDP1Playlist(jsonData);
if (result.error) {
console.error('Validation failed:', result.error.message);
// Access detailed error information
result.error.details?.forEach(detail => {
console.error(` ${detail.path}: ${detail.message}`);
});
} else {
console.log('Valid playlist:', result.playlist);
}Signing a Playlist
import { signDP1Playlist } from 'dp1-js';
const playlist = {
dpVersion: '1.0.0',
id: 'playlist-123',
slug: 'my-playlist',
title: 'My Playlist',
items: [
{
id: 'item-1',
title: 'Artwork 1',
source: 'https://example.com/artwork1.html',
duration: 30,
license: 'open',
created: '2025-01-01T00:00:00Z',
},
],
};
// Sign with Ed25519 private key (as hex string or Uint8Array)
const signature = await signDP1Playlist(
playlist,
privateKeyHex // or privateKeyBytes as Uint8Array
);
console.log('Signature:', signature);
// Output: "ed25519:0x<hex_signature>"
// Add signature to playlist
const signedPlaylist = {
...playlist,
signature,
};Verifying a Playlist Signature
import { verifyPlaylistSignature } from 'dp1-js';
// Playlist with signature
const signedPlaylist = {
dpVersion: '1.0.0',
id: 'playlist-123',
slug: 'my-playlist',
title: 'My Playlist',
items: [
{
id: 'item-1',
title: 'Artwork 1',
source: 'https://example.com/artwork1.html',
duration: 30,
license: 'open',
created: '2025-01-01T00:00:00Z',
},
],
signature: 'ed25519:0x...',
};
// Verify with Ed25519 public key (Uint8Array)
const isValid = await verifyPlaylistSignature(signedPlaylist, publicKeyBytes);
if (isValid) {
console.log('✓ Signature is valid');
} else {
console.log('✗ Signature verification failed');
}Parsing & Validating a Channel
import { parseChannel } from 'dp1-js';
// Parse and validate channel JSON
const result = parseChannel(channelData);
if (result.error) {
console.error('Validation failed:', result.error.message);
result.error.details?.forEach(detail => {
console.error(` ${detail.path}: ${detail.message}`);
});
} else {
console.log('Valid channel:', result.channel);
}Signing a Channel
import { signChannel } from 'dp1-js';
const channel = {
id: 'channel-123',
slug: 'my-channel',
title: 'My Channel',
playlists: ['https://example.com/playlist1', 'https://example.com/playlist2'],
created: '2025-01-01T00:00:00Z',
};
// Sign with Ed25519 private key (as hex string or Uint8Array)
const signature = await signChannel(channel, privateKeyHex);
console.log('Signature:', signature);
// Output: "ed25519:0x<hex_signature>"
// Add signature to channel
const signedChannel = {
...channel,
signature,
};Verifying a Channel Signature
import { verifyChannelSignature } from 'dp1-js';
// Channel with signature
const signedChannel = {
id: 'channel-123',
slug: 'my-channel',
title: 'My Channel',
playlists: ['https://example.com/playlist1'],
signature: 'ed25519:0x...',
};
// Verify with Ed25519 public key (Uint8Array)
const isValid = await verifyChannelSignature(signedChannel, publicKeyBytes);
if (isValid) {
console.log('✓ Signature is valid');
} else {
console.log('✗ Signature verification failed');
}API Reference
parseDP1Playlist(json: unknown): DP1PlaylistParseResult
Parses and validates playlist data from unknown JSON input.
Parameters:
json- Unknown JSON data to parse and validate
Returns: DP1PlaylistParseResult object containing either:
playlist- The validatedPlaylistobject (if successful)error- Detailed error information (if validation failed)type:"invalid_json"|"validation_error"message: Human-readable error messagedetails: Array of specific validation errors with paths
Example:
const result = parseDP1Playlist(data);
if (result.playlist) {
// Use validated playlist
console.log(result.playlist.title);
}signDP1Playlist(playlist: Omit<Playlist, "signature">, privateKey: Uint8Array | string): Promise<string>
Signs a playlist using Ed25519 as per DP-1 specification.
Parameters:
playlist- Playlist object without signature fieldprivateKey- Ed25519 private key as hex string or Uint8Array
Returns: Promise resolving to signature string in format "ed25519:0x<hex>"
Example:
const sig = await signDP1Playlist(playlist, '0x1234...');verifyPlaylistSignature(playlist: Playlist, publicKey: Uint8Array): Promise<boolean>
Verifies a playlist's Ed25519 signature using the provided public key.
Parameters:
playlist- Playlist object with signature fieldpublicKey- Ed25519 public key as Uint8Array (32 bytes)
Returns: Promise resolving to true if signature is valid, false otherwise
Example:
const isValid = await verifyPlaylistSignature(signedPlaylist, publicKeyBytes);
if (isValid) {
console.log('Signature verified successfully');
}Note: The function returns false if:
- The playlist has no signature
- The signature format is invalid
- The signature doesn't match the playlist content
- The public key is invalid or doesn't match the private key used for signing
parseChannel(json: unknown): ChannelParseResult
Parses and validates channel data from unknown JSON input.
Parameters:
json- Unknown JSON data to parse and validate
Returns: ChannelParseResult object containing either:
channel- The validatedChannelobject (if successful)error- Detailed error information (if validation failed)type:"invalid_json"|"validation_error"message: Human-readable error messagedetails: Array of specific validation errors with paths
Example:
const result = parseChannel(data);
if (result.channel) {
// Use validated channel
console.log(result.channel.title);
}signChannel(channel: Omit<Channel, "signature">, privateKey: Uint8Array | string): Promise<string>
Signs a channel using Ed25519 as per DP-1 specification.
Parameters:
channel- Channel object without signature fieldprivateKey- Ed25519 private key as hex string or Uint8Array
Returns: Promise resolving to signature string in format "ed25519:0x<hex>"
Example:
const sig = await signChannel(channel, '0x1234...');verifyChannelSignature(channel: Channel, publicKey: Uint8Array): Promise<boolean>
Verifies a channel's Ed25519 signature using the provided public key.
Parameters:
channel- Channel object with signature fieldpublicKey- Ed25519 public key as Uint8Array (32 bytes)
Returns: Promise resolving to true if signature is valid, false otherwise
Example:
const isValid = await verifyChannelSignature(signedChannel, publicKeyBytes);
if (isValid) {
console.log('Signature verified successfully');
}Types
The library exports comprehensive TypeScript types for DP-1 playlists and channels:
// Functions
import {
parseDP1Playlist,
parseChannel,
signDP1Playlist,
verifyPlaylistSignature,
signChannel,
verifyChannelSignature,
} from 'dp1-js';
// Types
import type {
Playlist,
Channel,
PlaylistItem,
DisplayPrefs,
Provenance,
Repro,
Entity,
DynamicQuery,
DP1PlaylistParseResult,
ChannelParseResult,
} from 'dp1-js';Core Types
Playlist Types
Playlist- Complete playlist structure with metadata and itemsPlaylistItem- Individual item in a playlistDisplayPrefs- Display preferences for artwork renderingProvenance- On-chain or off-chain provenance informationRepro- Reproduction and verification metadataEntity- Curator or publisher informationDynamicQuery- Dynamic query configuration for live content
Channel Types
Channel- Complete channel structure with playlist references and metadata
Utility Types
DP1PlaylistParseResult- Result type from parsing playlist operationChannelParseResult- Result type from parsing channel operation
See types.ts for complete type definitions.
Validators (schema-agnostic)
This package exposes small validation helpers that do not require consumers to import our internal schemas.
Available validators:
validateDpVersion(version: string)→ ValidationResultvalidateDisplayPrefs(input: unknown)→ ValidationResultvalidateRepro(input: unknown)→ ValidationResultvalidateProvenance(input: unknown)→ ValidationResultvalidatePlaylistItem(input: unknown)→ ValidationResultvalidateDynamicQuery(input: unknown)→ ValidationResultvalidateEntity(input: unknown)→ ValidationResultvalidateChannel(input: unknown)→ ValidationResult
Usage examples:
import { validateProvenance } from 'dp1-js';
const provenance = {
type: 'onChain',
contract: { chain: 'evm', address: '0xabc', tokenId: '42' },
};
const res = validateProvenance(provenance);
if (!res.success) {
console.error(res.error.message);
res.error.issues.forEach(i => console.error(`${i.path}: ${i.message}`));
}ValidationResult shape:
type ValidationIssue = { path: string; message: string };
type ValidationResult =
| { success: true }
| { success: false; error: { message: string; issues: ValidationIssue[] } };You can also integrate these validators into your own schema library (e.g., Zod) via .refine or .superRefine to attach issues to your app's error format.
Playlist Structure
A valid DP-1 playlist includes:
{
dpVersion: "1.0.0", // DP-1 protocol version
id: "unique-id", // Unique playlist identifier
slug: "url-friendly-slug", // URL-friendly identifier
title: "Playlist Title", // Human-readable title
created?: "ISO-8601-date", // Optional creation timestamp
defaults?: { // Optional default settings
display?: {...},
license?: "open" | "token" | "subscription",
duration?: 30
},
items: [...], // Array of playlist items
signature?: "ed25519:0x..." // Optional Ed25519 signature
}Channel Structure
A valid Feral File channel includes:
{
id: "uuid", // Unique channel identifier
slug: "url-friendly-slug", // URL-friendly identifier
title: "Channel Title", // Human-readable title
created?: "ISO-8601-date", // Optional creation timestamp
curator?: "string", // Optional curator name
curators?: [...], // Optional array of curator entities
summary?: "string", // Optional channel description
publisher?: {...}, // Optional publisher entity
playlists: [...], // Array of playlist URLs
coverImage?: "URI", // Optional cover image URI
signature?: "ed25519:0x..." // Optional Ed25519 signature
}Development
Setup
npm installBuild
npm run buildBuilds ESM, CJS, and TypeScript declaration files to dist/.
Test
npm testLint
npm run lintRequirements
- Node.js: 22+ (uses native
node:cryptowith Ed25519 support) - Browsers: Modern browsers with Web Crypto API support
How It Works
- Parsing & Validation: Uses Zod schemas to validate playlist structure against DP-1 specification
- Canonicalization: Implements RFC 8785 JSON canonicalization for deterministic signing and verification
- Signing: Uses Ed25519 signatures via Web Crypto API (available in Node 22+ and modern browsers)
- SHA-256 Hashing: Creates hash of canonical JSON before signing
- Verification: Validates signatures by comparing Ed25519 signature against playlist canonical form using public key
License
Contributing
Contributions are welcome! Please ensure:
- All tests pass (
npm test) - Code follows the existing style (
npm run lint) - TypeScript types are properly defined
Related
- DP-1 Specification - Official DP-1 protocol specification
- Feral File - Digital art platform using DP-1
Support
For issues and questions:
- Open an issue on GitHub
- Check the DP-1 specification for protocol details
