Skip to main content

API Reference

This reference covers the MotionPrint JavaScript/TypeScript SDK for embedding and verifying cryptographic watermarks in animation files.

Installationโ€‹

npm install @thirdrez/motionprint
# or
pnpm add @thirdrez/motionprint

Quick Startโ€‹

import { embedGLB, verifyGLB, generateNonce } from '@thirdrez/motionprint';

// Embed a watermark
const signedGlb = await embedGLB(
originalBuffer,
'animation.glb',
payload,
privateKeyHex
);

// Verify a watermark
const result = await verifyGLB(
signedGlb,
'animation.glb',
publicKeyHex
);

Typesโ€‹

Payloadโ€‹

The watermark payload containing provenance information.

interface Payload {
/** Creator identifier (max 64 chars) */
creatorId: string;

/** License identifier: 'restricted' | 'commercial' | 'cc-by' | etc. */
licenseId: string;

/** ISO 8601 timestamp */
issuedAt: string;

/** Embedding method */
method: 'extras-v1' | 'bvh-header-v1';

/** 128-bit hex nonce (32 chars) */
nonce: string;
}

VerificationResultโ€‹

The result of a verification operation.

interface VerificationResult {
/** True if signature is valid */
ok: boolean;

/** SHA-256 hash of canonical payload */
wmHash: string;

/** File metadata */
fileMeta: {
name: string;
size: number;
type: 'glb' | 'bvh';
};

/** Extracted watermark (if present) */
watermark?: Payload;

/** Ed25519 signature hex (if present) */
signature?: string;

/** Error reason (if !ok) */
reason?: string;
}

Core Functionsโ€‹

embedGLBโ€‹

Embeds a signed watermark into a GLB file.

function embedGLB(
buffer: ArrayBuffer,
filename: string,
payload: Payload,
privateKeyHex: string
): Promise<ArrayBuffer>

Parameters:

NameTypeDescription
bufferArrayBufferOriginal GLB file contents
filenamestringFilename (for metadata)
payloadPayloadWatermark payload
privateKeyHexstringEd25519 private key (hex encoded)

Returns: Promise<ArrayBuffer> - The signed GLB file

Example:

const payload: Payload = {
creatorId: 'studio_alpha',
licenseId: 'commercial',
issuedAt: new Date().toISOString(),
method: 'extras-v1',
nonce: generateNonce(),
};

const signed = await embedGLB(glbBuffer, 'walk.glb', payload, PRIVATE_KEY);
downloadFile(signed, 'walk_signed.glb');

verifyGLBโ€‹

Verifies a MotionPrint watermark in a GLB file.

function verifyGLB(
buffer: ArrayBuffer,
filename: string,
publicKeyHex: string
): Promise<VerificationResult>

Parameters:

NameTypeDescription
bufferArrayBufferGLB file contents
filenamestringFilename (for metadata)
publicKeyHexstringEd25519 public key (hex encoded)

Returns: Promise<VerificationResult>

Example:

const result = await verifyGLB(glbBuffer, 'animation.glb', PUBLIC_KEY);

if (result.ok) {
console.log('Creator:', result.watermark?.creatorId);
console.log('License:', result.watermark?.licenseId);
console.log('Registered:', result.watermark?.issuedAt);
} else {
console.error('Verification failed:', result.reason);
}

embedBVHโ€‹

Embeds a signed watermark into a BVH file.

function embedBVH(
buffer: ArrayBuffer,
filename: string,
payload: Payload,
privateKeyHex: string
): Promise<ArrayBuffer>

Same interface as embedGLB but for BVH files.


verifyBVHโ€‹

Verifies a MotionPrint watermark in a BVH file.

function verifyBVH(
buffer: ArrayBuffer,
filename: string,
publicKeyHex: string
): Promise<VerificationResult>

Same interface as verifyGLB but for BVH files.


generateNonceโ€‹

Generates a cryptographically secure 128-bit nonce.

function generateNonce(): string

Returns: 32-character hex string

Example:

const nonce = generateNonce();
// Returns: "f707614d6a07ff66d51933d6eeb3e0b0"

canonicalPayloadโ€‹

Creates the canonical string representation of a payload for signing.

function canonicalPayload(payload: Payload): string

Returns: JSON string with ordered keys + trailing newline

Example:

const msg = canonicalPayload(payload);
// Ready for signing

Low-Level Functionsโ€‹

extractWatermarkโ€‹

Extracts watermark data without verifying signature.

function extractWatermark(
buffer: ArrayBuffer,
type: 'glb' | 'bvh'
): { payload: Payload; signature: string } | null

signPayloadโ€‹

Signs a canonical payload with Ed25519.

function signPayload(
payload: Payload,
privateKeyHex: string
): Promise<string>

Returns: Hex-encoded signature (128 chars)


verifySignatureโ€‹

Verifies an Ed25519 signature.

function verifySignature(
message: string,
signatureHex: string,
publicKeyHex: string
): Promise<boolean>

Constantsโ€‹

/** Maximum creator ID length */
export const MAX_CREATOR_ID_LENGTH = 64;

/** MotionPrint protocol version */
export const PROTOCOL_VERSION = '1.0';

/** Supported file types */
export const SUPPORTED_FORMATS = ['glb', 'bvh'] as const;

Error Handlingโ€‹

The SDK throws typed errors for common issues:

import { 
MotionPrintError,
InvalidPayloadError,
SigningError,
UnsupportedFormatError
} from '@thirdrez/motionprint';

try {
const signed = await embedGLB(buffer, 'test.glb', payload, key);
} catch (error) {
if (error instanceof InvalidPayloadError) {
console.error('Bad payload:', error.field);
} else if (error instanceof SigningError) {
console.error('Signing failed:', error.message);
} else if (error instanceof UnsupportedFormatError) {
console.error('Format not supported');
}
}

Browser Usageโ€‹

For browser environments, import from the ESM bundle:

<script type="module">
import { verifyGLB } from 'https://cdn.thirdrez.com/motionprint/v1.0/motionprint.esm.js';

const fileInput = document.getElementById('file');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const buffer = await file.arrayBuffer();
const result = await verifyGLB(buffer, file.name, PUBLIC_KEY);
console.log(result);
});
</script>

React Hookโ€‹

import { useMotionPrintVerify } from '@thirdrez/motionprint/react';

function VerifyComponent() {
const { verify, result, isLoading, error } = useMotionPrintVerify(PUBLIC_KEY);

const handleFile = async (file: File) => {
await verify(file);
};

return (
<div>
<input type="file" onChange={(e) => handleFile(e.target.files[0])} />
{isLoading && <p>Verifying...</p>}
{result?.ok && <p>โœ“ Verified: {result.watermark?.creatorId}</p>}
{error && <p>Error: {error.message}</p>}
</div>
);
}

Rate Limitsโ€‹

The registry API has the following limits:

OperationLimit
Registration100 / hour per creator
Certificate fetch1000 / hour per IP
Batch registration50 files / request

Note: Verification is entirely client-side and has no rate limits.


Need help integrating? Contact [email protected]