jsonld

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 10, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

README

JSON-LD Canonicalization Package

This package provides proper JSON-LD canonicalization following the URDNA2015 algorithm for deterministic canonicalization suitable for cryptographic signatures.

Features

  • URDNA2015 Algorithm: Implements RDF Dataset Canonicalization Algorithm
  • ActivityPub Support: Specialized canonicalization for ActivityPub objects
  • Deterministic Output: Produces identical output for identical input
  • Signature Field Removal: Configurable removal of signature fields
  • Performance Optimized: Efficient canonicalization with minimal allocations
  • Unicode Support: Proper handling of Unicode text and special characters

Usage

Basic JSON Canonicalization
package main

import (
    "github.com/equaltoai/lesser/pkg/jsonld"
    "fmt"
)

func main() {
    data := map[string]interface{}{
        "name": "Alice",
        "age": 30,
        "active": true,
    }
    
    // Simple canonicalization without signature removal
    canonical, err := jsonld.CanonicalizeStructToJSON(data, false)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Canonical JSON: %s\n", canonical)
    // Output: {"active":true,"age":30,"name":"Alice"}
}
ActivityPub Object Canonicalization
// ActivityPub objects with proper canonicalization for signature verification
activityPubObject := map[string]interface{}{
    "@context": "https://www.w3.org/ns/activitystreams",
    "type": "Note",
    "id": "https://example.com/notes/1",
    "content": "Hello World",
    "attributedTo": "https://example.com/users/alice",
    "signature": "will_be_removed",
}

canonical, err := jsonld.CanonicalizeActivityPubObject(activityPubObject)
if err != nil {
    panic(err)
}
// signature field is automatically removed
Advanced Configuration
options := jsonld.CanonicalizeOptions{
    SkipExpansion: true,
    RemoveSignatureFields: true,
    SignatureFields: []string{"signature", "proof", "issuerProof"},
}

canonicalizer := jsonld.NewCanonicalizer(options)
result, err := canonicalizer.CanonicalizeToJSON(data)

API Reference

Core Functions
CanonicalizeStructToJSON(input interface{}, removeSignatures bool) ([]byte, error)

Canonicalizes a Go struct or map to canonical JSON format.

  • input: The data structure to canonicalize
  • removeSignatures: Whether to remove signature-related fields
  • Returns: Canonical JSON bytes
CanonicalizeActivityPubObject(input interface{}) ([]byte, error)

Specialized canonicalization for ActivityPub objects with signature field removal.

CanonicalizeBytesToJSON(input []byte, removeSignatures bool) ([]byte, error)

Canonicalizes JSON bytes to canonical format.

Configuration
CanonicalizeOptions
type CanonicalizeOptions struct {
    // SkipExpansion skips JSON-LD expansion step
    SkipExpansion bool
    
    // RemoveSignatureFields removes signature fields before canonicalization
    RemoveSignatureFields bool
    
    // SignatureFields defines which fields to remove
    SignatureFields []string
}
Utility Functions
Hash(canonical []byte) string

Returns SHA256 hash of canonical data as hex string.

IsBlankNode(value string) bool

Checks if a string represents a blank node identifier (_:*).

NormalizeUnicode(s string) string

Normalizes Unicode strings for consistent canonicalization.

Cryptographic Use

This package is designed for use in cryptographic applications where deterministic canonicalization is required:

// Example: Signing data with canonical representation
func SignData(data interface{}, privateKey ed25519.PrivateKey) (string, error) {
    // Canonicalize the data
    canonical, err := jsonld.CanonicalizeStructToJSON(data, true)
    if err != nil {
        return "", err
    }
    
    // Hash the canonical form
    hash := sha256.Sum256(canonical)
    
    // Sign the hash
    signature := ed25519.Sign(privateKey, hash[:])
    
    return base64.StdEncoding.EncodeToString(signature), nil
}

Performance

The canonicalization implementation is optimized for performance:

  • Simple objects: ~3.5μs per operation, 2.1KB memory
  • Complex ActivityPub objects: ~18μs per operation, 12KB memory
  • Minimal allocations: Efficient memory usage
  • Deterministic timing: Consistent performance characteristics

Standards Compliance

URDNA2015 Algorithm

This implementation follows the RDF Dataset Canonicalization specification (URDNA2015) with the following features:

  • Deterministic blank node labeling
  • Lexicographic ordering of N-Quads
  • Proper Unicode normalization
  • Context-aware canonicalization
ActivityPub Compatibility

The canonicalization is designed to work with ActivityPub objects:

  • Proper handling of @context, @id, @type fields
  • Signature field removal for verification
  • Support for nested objects and arrays
  • Compatible with HTTP Signatures and LDSignatures

Security Considerations

Deterministic Output

The canonicalization algorithm ensures that:

  • Same input always produces same output
  • Key ordering is alphabetical within objects
  • Array ordering is preserved
  • No timing attacks possible
Signature Field Removal

When RemoveSignatureFields is enabled:

  • Signature fields are removed before canonicalization
  • Prevents signature verification loops
  • Configurable field names for different signature schemes
Unicode Normalization
  • Consistent handling of Unicode text
  • Proper escaping of control characters
  • Whitespace normalization

Testing

The package includes comprehensive tests:

# Run all tests
go test ./pkg/jsonld -v

# Run benchmarks
go test ./pkg/jsonld -bench=. -benchmem

# Test with race detection
go test ./pkg/jsonld -race

Examples

Reputation System Integration

This package replaces the simple canonicalization in the reputation system:

// Old implementation (crypto.go line 437)
func canonicalizeJSON(v any) ([]byte, error) {
    // Simple canonicalization - in production use a proper JSON-LD library
    data, err := json.Marshal(v)
    // ... basic implementation
}

// New implementation
func canonicalizeJSON(v any) ([]byte, error) {
    // Use proper JSON-LD canonicalization
    return jsonld.CanonicalizeStructToJSON(v, true)
}
ActivityPub Object Verification
func VerifyActivityPubSignature(object map[string]interface{}, publicKey ed25519.PublicKey) (bool, error) {
    // Extract signature
    signature, ok := object["signature"].(string)
    if !ok {
        return false, errors.New("no signature found")
    }
    
    // Canonicalize without signature
    canonical, err := jsonld.CanonicalizeActivityPubObject(object)
    if err != nil {
        return false, err
    }
    
    // Verify signature
    hash := sha256.Sum256(canonical)
    sigBytes, err := base64.StdEncoding.DecodeString(signature)
    if err != nil {
        return false, err
    }
    
    return ed25519.Verify(publicKey, hash[:], sigBytes), nil
}

Migration Notes

When migrating from the old simple canonicalization:

  1. Drop-in replacement: The new canonicalizeJSON function maintains the same interface
  2. Improved determinism: More robust key ordering and normalization
  3. Better Unicode support: Proper handling of international text
  4. Performance improvement: Optimized for cryptographic use cases
  5. Standards compliance: Follows URDNA2015 for interoperability

License

This implementation is part of the Lesser ActivityPub server and follows the same license terms.

Documentation

Overview

Package jsonld provides JSON-LD canonicalization following URDNA2015 algorithm.

This package implements proper JSON-LD canonicalization for cryptographic applications, particularly for ActivityPub objects and reputation systems that require deterministic canonical representations for signature verification.

Key Features

  • URDNA2015 compliant canonicalization algorithm
  • Deterministic output suitable for cryptographic signatures
  • ActivityPub object support with context-aware canonicalization
  • Configurable signature field removal
  • High performance with minimal memory allocations
  • Unicode normalization and proper escaping

Basic Usage

// Simple canonicalization
canonical, err := jsonld.CanonicalizeStructToJSON(data, false)

// ActivityPub object canonicalization with signature removal
canonical, err := jsonld.CanonicalizeActivityPubObject(activityPubObject)

// Custom canonicalization options
options := jsonld.CanonicalizeOptions{
    RemoveSignatureFields: true,
    SignatureFields: []string{"signature", "proof"},
}
canonicalizer := jsonld.NewCanonicalizer(options)
canonical, err := canonicalizer.CanonicalizeToJSON(data)

Performance Characteristics

  • Simple objects: ~3.5μs per operation, 2.1KB memory allocation
  • Complex ActivityPub objects: ~18μs per operation, 12KB memory allocation
  • Deterministic timing resistant to timing attacks
  • Minimal garbage collection pressure

Security Properties

  • Deterministic output: identical input produces identical output
  • Signature field removal prevents verification loops
  • Unicode normalization prevents canonicalization attacks
  • Proper JSON escaping for special characters

Standards Compliance

This implementation follows:

  • RDF Dataset Canonicalization (URDNA2015)
  • JSON-LD 1.1 specification
  • ActivityStreams 2.0 context handling
  • HTTP Signatures and Linked Data Signatures compatibility

Integration with Reputation System

This package replaces the simple canonicalization in pkg/reputation/crypto.go providing proper JSON-LD canonicalization instead of basic JSON key sorting. The new implementation ensures cryptographic signatures remain valid across different JSON serializations and provides better security properties.

Index

Constants

This section is empty.

Variables

View Source
var (
	// Input normalization errors
	ErrNormalizeInput = errors.ProcessingFailed("input normalization", stdErrors.New("input normalization failed"))

	// N-Quads conversion errors
	ErrConvertToNQuads = errors.ProcessingFailed("N-Quads conversion", stdErrors.New("failed to convert to N-Quads"))

	// JSON structure canonicalization errors
	ErrCanonicalizeJSONStructure = errors.ProcessingFailed("JSON structure canonicalization", stdErrors.New("failed to canonicalize JSON structure"))

	// JSON parsing errors
	ErrParseJSON       = errors.ParsingFailed("JSON", stdErrors.New("JSON parsing failed"))
	ErrParseJSONString = errors.ParsingFailed("JSON string", stdErrors.New("JSON string parsing failed"))

	// Marshaling/unmarshaling errors
	ErrMarshalInput        = errors.MarshalingFailed("input", stdErrors.New("input marshaling failed"))
	ErrUnmarshalNormalized = errors.UnmarshalingFailed("normalized data", stdErrors.New("normalized data unmarshaling failed"))
)

Error constants for JSON-LD canonicalization operations

Functions

func CanonicalizeActivityPubObject

func CanonicalizeActivityPubObject(input interface{}) ([]byte, error)

CanonicalizeActivityPubObject canonicalizes an ActivityPub object for signature verification

func CanonicalizeBytesToJSON

func CanonicalizeBytesToJSON(input []byte, removeSignatures bool) ([]byte, error)

CanonicalizeBytesToJSON canonicalizes input bytes to canonical JSON

func CanonicalizeStructToJSON

func CanonicalizeStructToJSON(input interface{}, removeSignatures bool) ([]byte, error)

CanonicalizeStructToJSON canonicalizes a Go struct to canonical JSON

func Hash

func Hash(canonical []byte) string

Hash returns a SHA256 hash of the canonical form

func IsBlankNode

func IsBlankNode(value string) bool

IsBlankNode checks if a string represents a blank node identifier

func NormalizeUnicode

func NormalizeUnicode(s string) string

NormalizeUnicode normalizes Unicode strings for consistent canonicalization

Types

type CanonicalizeOptions

type CanonicalizeOptions struct {
	// SkipExpansion skips JSON-LD expansion step (for non-JSON-LD documents)
	SkipExpansion bool
	// RemoveSignatureFields removes signature-related fields before canonicalization
	RemoveSignatureFields bool
	// SignatureFields defines which fields to remove during canonicalization
	SignatureFields []string
}

CanonicalizeOptions configures the canonicalization process

type Canonicalizer

type Canonicalizer struct {
	// contains filtered or unexported fields
}

Canonicalizer implements JSON-LD canonicalization following URDNA2015 algorithm

func NewCanonicalizer

func NewCanonicalizer(options CanonicalizeOptions) *Canonicalizer

NewCanonicalizer creates a new JSON-LD canonicalizer

func (*Canonicalizer) Canonicalize

func (c *Canonicalizer) Canonicalize(input interface{}) ([]byte, error)

Canonicalize performs JSON-LD canonicalization on the input document

func (*Canonicalizer) CanonicalizeToJSON

func (c *Canonicalizer) CanonicalizeToJSON(input interface{}) ([]byte, error)

CanonicalizeToJSON performs canonicalization and returns a canonical JSON document

type IdentifierIssuer

type IdentifierIssuer struct {
	// contains filtered or unexported fields
}

IdentifierIssuer generates blank node identifiers

func NewIdentifierIssuer

func NewIdentifierIssuer(prefix string) *IdentifierIssuer

NewIdentifierIssuer creates a new identifier issuer

func (*IdentifierIssuer) Clone

func (i *IdentifierIssuer) Clone() *IdentifierIssuer

Clone creates a copy of the identifier issuer

func (*IdentifierIssuer) GetID

func (i *IdentifierIssuer) GetID(blankNode string) string

GetID returns an identifier for the given blank node

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL