jcs

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Mar 29, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

jcs — JSON Canonicalization (RFC 8785)

Go Report Card godoc GitHub license GitHub go.mod Go version Stable

Cryptographic hashing and signing need a stable byte representation of JSON. The same logical value can be serialized many ways — key order, spacing, number formatting — making naive JSON unsuitable as a signing payload.

JCS (RFC 8785) solves this by defining a canonical form:

  • Serializes primitives like ECMAScript JSON.stringify
  • Restricts to the I-JSON subset (RFC 7493)
  • Sorts object keys by UTF-16 code unit order, recursively
  • Preserves array element order

This library is a v1.0 stable implementation in Go. The public API (Transform, NumberToJSON) will not have breaking changes within the v1 major version.


Install

Requires Go 1.24+.

go get github.com/deszhou/jcs

Usage

Transform — canonicalize a JSON document
import "github.com/deszhou/jcs"

canonical, err := jcs.Transform(jsonBytes)
if err != nil {
    log.Fatal(err)
}
// canonical is a UTF-8 JCS byte slice, safe to hash or sign directly

Transform accepts any valid JSON root value: object, array, or scalar (true / null / number / string). Leading and trailing whitespace around the root value is allowed.

Typical use — sign a canonical payload:

canonical, err := jcs.Transform(jsonBytes)
if err != nil {
    return err
}
digest := sha256.Sum256(canonical)
signature := sign(privateKey, digest[:])
NumberToJSON — ES6-style float64 formatting
formatted := jcs.NumberToJSON(1e30)   // "1e+30"
formatted  = jcs.NumberToJSON(4.5)    // "4.5"
formatted  = jcs.NumberToJSON(0.002)  // "0.002"

Use this when you need to format a standalone float64 in JCS-compatible form — for example, when constructing a canonical payload manually rather than round-tripping through json.Marshal.


Errors

Transform returns an error for any input that violates RFC 8785 or I-JSON constraints:

Condition Example
Invalid JSON {key: value}
Duplicate object keys {"a":1,"a":2}
Number out of safe-integer range integers beyond ±2⁵³
Lone UTF-16 surrogate in a string "\uD800" (unpaired)

Example

Input:

{
  "numbers": [333333333.33333329, 1E30, 4.50, 2e-3, 0.000000000000000000000000001],
  "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
  "literals": [null, true, false]
}

Canonical output (single line, keys sorted, numbers normalized):

{"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":"€$\u000f\nA'B\"\\\\\"/"}

Key differences from the input: "literals" sorts before "numbers" and "string"; numbers are normalized to ES6 form; control characters use the shortest valid escape sequence.


Performance

Benchmarks run on Apple M4 (darwin/arm64, Go 1.24). Each result is the average of 5 runs:

go test -bench=. -benchmem -count=5
Time per operation — lower is better
Scenario cyberphone gowebpki this library vs cyberphone vs gowebpki
Structures (nested object) 1878 ns 1708 ns 1428 ns −24% −16%
Arrays (mixed array) 595 ns 483 ns 471 ns −21% −2%
Unicode (non-ASCII values) 401 ns 374 ns 245 ns −39% −34%
Weird (special chars in keys) 2569 ns 2359 ns 1887 ns −27% −20%
Allocations per operation — lower is better
Scenario cyberphone gowebpki this library reduction
Structures 95 95 74 −22%
Arrays 27 27 23 −15%
Unicode 16 16 12 −25%
Weird 95 95 67 −29%

To reproduce, clone cyberphone/json-canonicalization and gowebpki/jcs alongside this repo, then run go test -bench=. -benchmem -count=5 in the comparison/ directory.

What drives the gains
Optimization Effect
256-byte escape lookup table O(1) escape decisions, eliminates per-byte branching
Direct \u00XX byte writes Removes one fmt.Sprintf call per control character
slices.SortFunc + slices.Compare Replaces container/list insertion sort for object keys
ASCII-only fast path in sort-key builder Avoids []rune allocation for pure-ASCII keys
strings.Builder.Grow pre-sized to len(input)+2 Cuts reallocations during string serialization

Contributing

Bug reports, test cases, and pull requests are welcome.

  1. Open an issue to discuss what you'd like to change before a large PR
  2. Run go test ./... and go vet ./... before submitting
  3. For benchmark changes, include before/after numbers in the PR description

Attribution

Derived from cyberphone/json-canonicalization (Anders Rundgren), the original multi-language JCS reference implementation. Licensed Apache-2.0 — see LICENSE.


See also

Documentation

Overview

Package jcs implements JSON Canonicalization Scheme (JCS) per RFC 8785.

Use Transform to convert UTF-8 JSON bytes into a canonical form suitable for stable hashing or signing. Number formatting follows ECMAScript rules; object keys are sorted by UTF-16 code unit order.

Reference: https://www.rfc-editor.org/rfc/rfc8785

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NumberToJSON

func NumberToJSON(ieeeF64 float64) (res string, err error)

NumberToJSON converts numbers in IEEE-754 double precision into the format specified for JSON in EcmaScript Version 6 and forward. The core application for this is canonicalization per RFC 8785:

func Transform

func Transform(jsonData []byte) ([]byte, error)

Transform converts raw JSON into its RFC 8785 canonical UTF-8 form. The input must be valid JSON; non-ASCII bytes are only allowed inside strings.

Types

This section is empty.

Directories

Path Synopsis
test
verify-canonicalization command
verify-canonicalization checks every file in testdata/input against the corresponding expected output in testdata/output and prints the UTF-8 hex representation of each result.
verify-canonicalization checks every file in testdata/input against the corresponding expected output in testdata/output and prints the UTF-8 hex representation of each result.
verify-numbers command
verify-numbers tests NumberToJSON against a set of discrete IEEE-754 values and, when available, against the 100-million-value test suite published at https://github.com/cyberphone/json-canonicalization.
verify-numbers tests NumberToJSON against a set of discrete IEEE-754 values and, when available, against the 100-million-value test suite published at https://github.com/cyberphone/json-canonicalization.

Jump to

Keyboard shortcuts

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