Skip to main content

Function: canonicalizeForSigning()

canonicalizeForSigning(info): string

Produces the exact byte string that is Ed25519-signed at publish time and verified at update time.

Only integrity- and rollout-critical fields are covered:

  • version — prevents version downgrade/substitution
  • stagingPercentage — prevents tampering with staged-rollout gating
  • minimumSystemVersion — prevents bypassing or forging the OS-version gate (its absence is signed too, so one cannot be added after the fact)
  • each file's url, sha512, size — the artifact identity + integrity hash the updater enforces
  • each NSIS web-installer package's arch key and path, sha512, size, blockMapSize, isAdminRightsRequired (WindowsUpdateInfo.packages, keyed by arch) — the payload the web installer downloads and verifies

Cosmetic/operational fields (releaseDate, releaseNotes, releaseName) are intentionally excluded so they can be edited post-signing without invalidating the signature. The signature/signatures fields are not part of the payload either, so signatures can be added or removed independently of one another. The deprecated top-level path/sha512 mirror of files[0] is not signed; instead the verifier refuses a signed manifest without a non-empty files list (see validateSignedManifestShape), so the updater never falls back to those legacy fields for a verified manifest.

Wire format (EBUM1): one record per line, label: followed by tab-separated fields, e.g.

EBUM1
version:"1.2.3"
staging:25
minimumSystemVersion:"10.0.19041"
file:"App-1.2.3.exe"<TAB>"<sha512>"<TAB>8123456
package:"x64"<TAB>"App-1.2.3-x64.nsis.7z"<TAB>"<sha512>"<TAB>5000<TAB>120<TAB>1

Every value goes through encodeField (JSON-quoted strings, bare numbers, empty for absent), which is what makes the encoding injective: a value can never contain an unescaped newline or tab, so no choice of field values in one manifest can reproduce the record structure of another (e.g. an empty files list plus a minimumSystemVersion that spells out the original file: lines). The format is deterministic regardless of object key order or YAML formatting: file records are sorted, package records are sorted, and a version prefix anchors the scheme. Both the signer (build) and verifier (runtime) MUST call this identical function — it is a wire contract — and both operate on the manifest exactly as written to latest*.yml: the signer runs on the final UpdateInfo right before serialization, the verifier on the parsed manifest before the provider resolves files[].url / packages[arch].path against the feed base URL. This function never throws; shape problems are reported by validateSignedManifestShape instead.

Parameters​

info​

UpdateInfo

Returns​

string