For the complete documentation index, see llms.txt
Compact toolchain 0.35.0
- Date: 2026-09-28
- Language version: 0.27.0
- Compact runtime version: 0.20.0
- Environment: This release works with a Midnight ledger 9 blockchain. For the versions that each public network supports, see the compatibility matrix.
Toolchain 0.35.0 targets ledger version 9, which is not yet deployed on Preview, Preprod, or Mainnet. Contracts compiled with it require Compact runtime 0.20.x, and npm's latest tag for @midnight-ntwrk/compact-runtime now points to 0.20.0. To build contracts for the public networks, run compact update 0.31 and pin the Compact runtime version listed in the compatibility matrix.
High-level summary
Version 0.35.0 of the Compact toolchain is a major release.
This release builds on 0.34.0. It adds the secp256r1 (P256) and Curve25519 curves, ECDSA verification over P256, Ed25519 signature verification and sha512 hashing, all of which require the flag --feature-zkir-v3. It also adds the kernel.caller() ledger operation, and cross-contract calls now resolve their callee's implementation at run time, which changes the Compact runtime's cross-contract API. Several changes are breaking; see Breaking changes.
compact update installs the latest released version, which is 0.35.0 at the time of this release, and compact update 0.35 installs the latest patch release of toolchain 0.35. If you are building contracts to deploy on the public networks today, continue to use Compact toolchain 0.31.x, which compact update 0.31 installs.
Audience
These release notes are for Compact smart contract developers and for DApp developers who use the Compact runtime.
Breaking changes
- The language version is 0.27.0. A contract that declares
pragma language_version 0.26;fails to compile withlanguage version 0.27.0 mismatch. Usepragma language_version 0.27;or a range such as>= 0.26. - Contracts compiled with this toolchain require Compact runtime 0.20.x. The generated code throws
Version mismatchwhen it loads with any other minor version of@midnight-ntwrk/compact-runtime, such as 0.16.0 or 0.19.0. - Any call to the standard library's
jubjubSchnorrVerifyorsecp256k1EcdsaVerifythat passes the identity point as the public key now fails an assertion, because doing so is inherently unsafe. createCircuitContexttakes a singleCircuitContextOptionsobject instead of positional parameters. The state provider and the newContractModuleProvidergo under one optionalcrossContractmember, andparentBlockHashstays outside it. ThereentrancyGuardparameter and theCircuitContext.reentrancyGuardfield are removed, and the re-entrancy guard always applies.crossContractCalltakes a singleCrossContractCallOptionsobject instead of positional arguments, with the required fieldsinterfaceNameanddeclaration. Generated code calls this function, so this affects you only if you call it directly.- The static dependency crawler,
contract-dependencies.ts, is removed, withcontractDependenciesand all of its types, includingContractReferenceLocations. Generated contract modules no longer exportcontractReferenceLocations. ContractInterfaceMismatchErroris removed. The runtime reports aModuleResolutionErrorwith anImplementationMismatchfailure instead.isEncodedContractAddressis removed.- Compiled contracts reject a
JubjubPointpassed in from JavaScript, as a circuit or constructor argument or as a witness result, with a coordinate outside[0, MAX_FIELD]. With--feature-zkir-v3, they also reject aSecp256k1Pointthat is not on the curve or has a coordinate outside[0, MAX_SECP256K1_BASE]. Toolchain 0.34.0 accepted these values at the circuit boundary. CompactTypeSecp256k1Point.fromValuerejects an identity flag that is neither 0 nor 1. Previously, any value other than 1 was read asfalse.CompactTypeUnsignedInteger.toValue,CompactTypeEnum.toValue, andCompactTypeBytes.toValuereject arguments they cannot encode faithfully: a value outside the type's range, a non-integer enum tag, or a byte string longer than the type's length.compactc --version,format-compact --version, andfixup-compact --versionprint the commit and its date after the version, for example0.35.0 (debb05f94 2026-09-29). If a script compares the whole output with a version string, read thereleasefield ofcompactc --version --verboseinstead.
Known issues
- A top-level call that reads
kernel.caller()fails on chain with a read mismatch whenever the balanced intent's unshielded inputs all belong to one user, for example when a user pays unshielded tokens to the contract. Off-chain execution recordsnonefor a top-level call, because the wallet adds the unshielded inputs when it balances the transaction, after proving has fixed the transcript. Until an intent can explicitly set the top-level caller, readkernel.caller()only where the call is known to come from a contract. - In Compact runtime 0.20.0, a
Secp256k1PointorSecp256r1Pointidentity value whosexoryis not 0 encodes differently from one with both coordinates 0, sopersistentHashcan give different digests for two identity points. Setxandyto 0 when you pass an identity point from JavaScript. The fix is on the main branch of the Compact repository (issue #795). - This release depends on pre-release components: ledger
9.1.0.0-rc.3, thezkir-v3binary fromzkir-3.1.0-rc.1, and@midnightntwrk/onchain-runtime-v44.0.0-rc.3.
What changed
This release includes all changes for compiler versions in the range between 0.34.100 and 0.35.0, language versions in the range between 0.26.100 and 0.27.0, and Compact runtime versions in the range between 0.19.100 and 0.20.0. The sections below list those changes by prerelease version, newest first, so some entries describe changes to features that first appeared in an earlier prerelease.
[Toolchain 0.34.113, language 0.26.106, runtime 0.19.106]
Changed
-
The
zkir-v3binary shipped with the compiler now comes from the midnight-zkir release candidatezkir-3.1.0-rc.1, whose binary encoding puts every ZKIR 3.0 instruction and type where compactc 0.34.0 did and appends the ZKIR 3.1 additions after them. Thezkir-v3shipped since 0.34.103 put those additions in the middle, so a ZKIR 3.0 reader could not decode the IR in its prover keys correctly. The release candidate also restores the ZKIR 3.0 circuit forencodeonBytes<32>. Instruction names and fields in.zkirfiles are unchanged.This change applies only with the flag
--feature-zkir-v3.
Fixed
compactc --feature-zkir-v3 --ledger-versionprints the version of thezkir-v3dependency,zkir-3.1.0-rc.1. Since 0.34.103 it printed the wholeflake.nixline declaring that dependency, because the version was read from amidnight-ledger/URL and the dependency had moved to midnight-zkir.
[Toolchain 0.34.112, language 0.26.106, runtime 0.19.106]
Added
- Compact reference and API documentation updates for the various new and modified language features, including dynamic cross-contract calls and the new foreign fields and points, plus various other documentation updates, corrections, and clarifications.
Fixed
- An issue in the type inferencer that could result in internal errors
rather than appropriately descriptive error messages for casts of foreign
field values to values of
FieldandUinttypes.
[Toolchain 0.34.111, language 0.26.106, runtime 0.19.106]
Added
-
The standard library has a new circuit
secp256r1EcdsaVerifythat verifies an ECDSA signature over the secp256r1 (also known as P256) curve and returns a boolean value telling whether the verification succeeded. Likesecp256k1EcdsaVerify, it asserts that the public key is not the identity. A public key recovered off-circuit with the runtime'ssecp256r1EcdsaRecovercan now be verified in circuit.This feature requires the flag
--feature-zkir-v3. -
The standard library has a new circuit
ed25519Verify<#n>that verifies an Ed25519 signature (RFC 8032) over ann-byte message and returns a boolean value telling whether the verification succeeded. The challenge is hashed in-circuit withsha512. It asserts that the public key is not the identity.This feature requires the flag
--feature-zkir-v3.
Changed
- Compiled contracts now also reject a
Curve25519Pointpassed in from JavaScript that is on the curve but outside the prime-order subgroup, that is, one with a small-order component. Circuits can only hold subgroup points, so such a point used to be accepted for off-circuit computation and then fail at proving time; it is now a type error.isValidCurve25519Pointchecks subgroup membership as well. secp256k1 and secp256r1 have cofactor 1, so they need no such check.
[Toolchain 0.34.110, language 0.26.105, runtime 0.19.105]
Added
-
Add
secp256r1EcdsaRecoverto the Compact JavaScript runtime. Given a 32-byte message hash, an ECDSA signature and a recovery id, it returns the corresponding secp256r1 public key.Recovery runs off-circuit, as it does for secp256k1. To constrain a recovered key in circuit, use
secp256r1EcdsaVerify(see toolchain 0.34.111 above).
Changed
- Compiled contracts now reject invalid
Secp256k1Point,Secp256r1PointandCurve25519Pointvalues passed in from JavaScript as circuit or constructor arguments or as witness results. A point is invalid if a coordinate is outside the curve's base field, or if it is not on the curve. An invalid point is now a type error instead of being used in computation.
[Toolchain 0.34.109, language 0.26.105, runtime 0.19.104]
Added
-
kernel.caller()ledger operation returns the caller of a circuit invocation asMaybe<PublicAddress>:left(addr)when called by contractaddr;right(addr)when this is a top-level call and every unshielded input of the containing intent is owned by useraddr(their unshielded address);noneotherwise, and always in a constructor.
The ledger derives the top-level value from the intent's unshielded inputs, which the wallet adds when it balances the transaction, after the transcript that read
callerwas fixed by proving. Off-chain execution recordsnonefor a top-level call, so such a call fails on chain with a read mismatch whenever the balanced intent's unshielded inputs all belong to one user.left(addr)is reliable: the runtime sets the calling contract for callees and the ledger gives a claiming contract precedence over the inputs. Until an intent can explicitly set the top-level caller, readkernel.caller()only where the call is known to come from a contract. -
PublicAddressstandard library type alias forEither<ContractAddress, UserAddress>.
[Toolchain 0.34.108, language 0.26.104, runtime 0.19.104]
Added
-
There is one new cast available, from
Bytes<64>toCurve25519Scalar. The semantics is the same as the other from-bytes casts for foreign fields: it performs modular reduction by the field modulus of the value represented by the byte vector.This feature requires the flag
--feature-zkir-v3.
[Toolchain 0.34.107, language 0.26.103, runtime 0.19.104]
Fixed
-
compactc --versionnow reports the release it was built from, including any prerelease identifier and the commit. It previously reported only the major.minor.bugfix triple, so every candidate for a release reported that release.The version a build reports is now a fact about the build. Builds that are not releases report
-dev: the scheduled build and the on-demand dev publish have no release to name, and a dev publish is installable, so one reporting the same shape as a finished release could pass for it.-devsorts below every release of the same triple, so a version check that wanted a release fails instead of passing. That is also the value committed incompiler/version-config.ss, so a build nothing stamped cannot pass for a release either.The commit is reported beside the version rather than inside it, for example
0.35.0 (debb05f94 2026-09-29), and is recorded in full incontract-info.jsonandcontract-manifest.jsonas a newcompiler-commitfield, leavingcompiler-versiona valid semver string. That string is what gets pinned in CI and compared by tooling, and semver build metadata is not reliably ignored in comparison, so a version carrying it reads as a different version.compactc --version --verbosereportsrelease,commit-hash,commit-date,language-versionandruntime-versionas separate fields, so a script need not parse one out of the other and a bug report needs one command rather than three. Fields the build did not record readunknownrather than being dropped, so the set of fields does not depend on how the compiler was built.Release candidates still satisfy the same
pragma compiler_versionconstraints as the release they are candidates for. -
The first of
--help,--version,--language-version,--ledger-versionand--runtime-versionon the command line is the one that acts. Flag actions used to run mid-parse, so--ledger-version --feature-zkir-v3reported the zkir-v2 ledger version (the feature flag had not been seen yet), while the reverse order reported v3. Both orders now report v3. -
format-compact --versionandfixup-compact --versionreport the commit and its date the same way ascompactc --version: the three tools share one version printer.
[Toolchain 0.34.106, language 0.26.103, runtime 0.19.104]
Added
-
The standard library now has support for
sha512hashing. The signature is likepersistentHash(that is, SHA-256) andkeccak256, except that the return type isBytes<64>. There is a corresponding functionsha512exported from the Compact runtime.This feature requires the flag
--feature-zkir-v3.
[Toolchain 0.34.105, language 0.26.102, runtime 0.19.103]
Added
-
The standard library now has support for the Curve25519 curve. It exports two new field types
Curve25519BaseandCurve25519Scalarand a new point typeCurve25519Point. They are similar to the secp256k1 and secp256r1 foreign curves, with the exception that the JavaScript point type does not have an identity flag. The curve is a twisted Edwards curve and the identity point is{ x: 0, y: 1 }.The fields and curve points support the same operations as the other foreign fields and curve points. The Compact runtime exports types, constants, and functions analogous to the ones for the other foreign fields and curves.
This feature requires the flag
--feature-zkir-v3.
[Toolchain 0.34.104, language 0.26.101, runtime 0.19.102]
- Any call to the Compact standard library implementations of
jubjubSchnorrVerifyandsecp256k1EcdsaVerifythat passes the identity point as the public key now results in a failed assertion, because doing so is inherently unsafe. This is a breaking change.
[Toolchain 0.34.103, language 0.26.100, runtime 0.19.102]
Added
-
The standard library now has support for the secp256r1 (also known as P256) curve. It exports two new field types,
Secp256r1BaseandSecp256r1Scalar, and a new point typeSecp256r1Point. These behave exactly as the similar secp256k1 curve.The fields support equals and not-equals comparisons (not relational comparisons) and the full set of arithmetic operations
+, (binary)-,*,neg, andinv. The point cannot be constructed in Compact code but it has accessorssecp256r1PointXandsecp256r1PointY. The point type supportsecAdd,ecMul, andecMulGenerator.The Compact runtime exports an interface
Secp256r1Pointfor the point type. The interface is identical toSecp256k1Point, with a read-onlyidentityboolean property to indicate the (additive) identity point. There are also descriptors and implementations of the arithmetic operations in the Compact runtime.The runtime also exports constants for the field modulus and the maximum field values for the new field types.
This feature requires the flag
--feature-zkir-v3.
Fixed
- Fixed a bug in
defaultvalues for secp256k1 field types in ZKIR. A literal 0 was used, which has typeScalar<BLS12-381>, so the resulting ZKIR code was not well-typed. The compiler now casts that value (indirectly throughBytes<32>) to the correct secp256k1 field type.
[Toolchain 0.34.102, language 0.26.0, runtime 0.19.101]
Fixed
The deserialize operator now requires the byte representing a Boolean
value to be either 0 or 1.
[Toolchain 0.34.101, language 0.26.0, runtime 0.19.101]
Added
- Cross-contract calls now resolve their callee's implementation at run time rather than importing it at compile time. A caller no longer names the module implementing the contract deployed at the call target: the application supplies one, and the runtime checks it against both the caller's contract type and the chain before entering it. Deploying a new implementation at an address no longer means recompiling its callers.
- Every generated contract module gains two tables:
declaredInterfaces: for each contract type the contract calls through, the circuit signatures that type declares. Passed tocrossContractCallat the call site, because the descriptor lives in the caller's module and the runtime is reached from it.circuitSignatures: one entry per external circuit name, carryingpure,provable,argumentTypesandresultType. This is the callee side of that comparison. Both appear in the emitted.d.ts. TheexpectedVktable is unchanged, but is now checked past the called circuit (see the key agreement entry below).
- Adds the runtime machinery behind the above. New modules, all re-exported
from the package index:
providers.ts:ContractModuleProvider, a user-suppliedresolve(address)returning aModuleThunkfor the module deployed there, orundefinedwhen the application has no binding for it.resolveis synchronous and total; loading is deferred into the thunk.module.ts:Module, the exports the runtime needs from a callee, plusContractCtor,ContractInstance,ProvableCircuit(s),PureCircuit(s).interface-descriptor.ts:SignatureType,InterfaceDescriptor,CircuitSignature(s),DeclaredInterfacesand friends: the type language the two emitted tables are written in.conformance.ts:checkConformanceandsignatureTypesEqual, which compare a resolved module's signatures against the caller's contract type under six rules (Existence,Purity,Provability,Arity,ArgumentType,ResultType), plusUnreadableSignaturefor a type constructor this runtime does not know.verifier-key-hash.ts: the brandedVerifierKeyHashandisVerifierKeyHash/asVerifierKeyHash/verifierKeyHashOf.module-resolution.ts:ModuleResolutionError, carrying aModuleResolutionFailurediscriminated union with eleven kinds:ModuleProviderAbsent,PureInterfaceCircuit,OperationAbsent,UnsupportedImplementation,ProviderThrew,NonconformantImplementation,UnreadableModule,MalformedVerifierKeyHash,ImplementationMismatch,ModuleLoadRejectedandIncompleteModule. A payload rather than an error subclass, so it survives an application re-throwing through its own error type. The last of these covers a module built before dynamic resolution: the exports resolution reads are checked as the module loads, so a stale artifact names itself instead of failing later as a type error inside conformance checking.
- Adds
CompactError.is, which recognizes runtime errors and their subclasses across duplicate installs of the package. A generated contract module resolves its own copy of the runtime, so the copy that throws is not the copy an application catches with, andinstanceoffails. - Key agreement extends past the called circuit: its fingerprint is mandatory
and must match, and every other circuit present in both
expectedVkand the deployed operations must agree. One circuit is too weak a check, since two versions of a contract agree on whatever they did not change; requiring the module's whole set is too strong, since removing an entry point would make the callee unusable for every other circuit.
Changed
- Breaking:
createCircuitContexttakes a singleCircuitContextOptionsobject rather than eleven positional parameters. The two providers are grouped under one optionalcrossContractmember, so an execution either can make cross-contract calls or cannot, with no half-provisioned combination in between.parentBlockHashstays outside the group: it also reaches the VM's block context. - Breaking:
crossContractCalltakes aCrossContractCallOptionsobject, with two new required fields,interfaceNameanddeclaration. - Conformance is checked before key agreement, so a module that does not implement the contract type is diagnosed as such rather than as a key mismatch.
Fixed
- A coin commitment created before a cross-contract call is no longer lost when
the call returns.
createZswapOutputrecorded the commitment only on the live call context, never on the per-address query-context map, so restoring the caller rewound it and a laterupdate-with-coin-checkledger op on that coin failed with "Coin commitment not found". The same write-back also means a second call into a contract resumes from the commitments its first turn made.
Removed
- Breaking: the static dependency crawler,
contract-dependencies.ts, and all of its exports have been removed. - Breaking: generated contract modules no longer export
contractReferenceLocations, which existed to feed the dependency crawler. It is gone from the emitted.d.tsas well. - Breaking:
ContractInterfaceMismatchError, replaced byModuleResolutionErrorwith anImplementationMismatchfailure. - Breaking:
reentrancyGuard, from bothCrossContractInputsandCircuitContext. The guard is unconditional: the ledger can mis-apply a re-entrant transcript, so there is no execution it is correct to skip it for. - Breaking:
isEncodedContractAddress, which lost its last caller with the dependency crawler. - The compiler no longer emits an import of the callee's module into a caller's generated code, since that is what the provider now supplies.
[Toolchain 0.34.100, language 0.26.0, runtime 0.19.100]
Fixed
-
CompactTypeOpaqueUint8Array.fromValueandCompactTypeOpaqueString.fromValuenow throw aCompactErroron exhausted input. Previously they returnedundefined(laundered through anas Uint8Arraycast) and""respectively. These were the last two descriptors that did not fail loudly on a truncated field-aligned binary value. -
CompactTypeEnum.fromValuenow reports an out-of-range value asexpected Enum[<=N]rather thanexpected UnsignedInteger[<=N]. -
CompactTypeBytes.fromValuenow returns a copy rather than aliasing the atom it decoded, so mutating a decoded value cannot mutate the field-aligned binary value it came from.
Changed
-
Breaking. Runtime type checks on curve point arguments to exported circuits now bound the coordinates rather than only checking their types. A
Secp256k1Pointwhosexoryfalls outside[0, MAX_SECP256K1_BASE], or aJubjubPointwhose coordinates fall outside[0, MAX_FIELD], is now rejected at the circuit boundary rather than failing later insidetoValue. This makes the check consistent with the one already applied to a bareSecp256k1BaseorFieldargument. -
Breaking.
CompactTypeSecp256k1Point.fromValuenow rejects an identity flag that is neither 0 nor 1. Previously any value other than 1 was silently read asfalse. -
Breaking.
CompactTypeUnsignedInteger.toValue,CompactTypeEnum.toValueandCompactTypeBytes.toValuenow reject arguments they cannot faithfully encode: a value outside the type's range, a non-integer enum tag, or a byte string longer than the type's length. Previously these descriptors validated on decode but not on encode, so it was possible to write a value to the ledger that could not be read back.