What does subtle.WithDataIndependentTiming do, and on which hardware does it matter?
answer
- it takes a function and runs it
- a processor mode, not an algorithm
- a plain call on most architectures
- arm64 FEAT_DIT and PSTATE.DIT
- goroutines started inside inherit it
basics
~20 ssubtle.WithDataIndependentTiming(f func()) runs f with the processor's data-independent-timing mode enabled. On arm64 chips with FEAT_DIT it sets PSTATE.DIT for the duration; on every other architecture it simply calls f. It does not make variable-time code constant-time.
solid answer
~50 sIt takes a `func()` and runs it, but on hardware that supports the feature it first turns on a processor mode in which certain instructions take the same time regardless of their operands. Today that means arm64 with FEAT_DIT, where it sets and then clears `PSTATE.DIT`; on every other architecture the call just invokes `f` with no other effect, so it is always safe to write. Two details matter in review. It does not fix variable-time code — the documentation says so explicitly — so it is a hardware backstop under code already written to be constant-time, never a substitute for `subtle.ConstantTimeCompare`. And the mode is inherited: goroutines `f` spawns, and their descendants, run with it enabled for their lifetime, as does C called through cgo from inside `f`. Calls may be nested, and the mode is restored from a deferred call, so a panic inside `f` still unwinds cleanly.
code
go · 7 linesvar ok bool
subtle.WithDataIndependentTiming(func() {
ok = subtle.ConstantTimeCompare(got, want) == 1
})
if !ok {
return errInvalid
}go deeper
You are unlikely to be asked this. If it comes up, it is enough to say it is a crypto/subtle helper that runs a function with a hardware timing mode enabled, and that on most machines it is just a plain call.
Be able to state the shape — it takes a func() and returns nothing — and the caveat from its own documentation: it does not turn variable-time code into constant-time code.
Know where it applies (arm64 with FEAT_DIT) and what it pulls in (goroutines spawned inside, and cgo calls), so you can argue why wrapping one comparison is right and wrapping a whole request handler is not.
The call to own is whether a hardware-conditional backstop belongs in your code at all. It costs a closure and a recurring review conversation on every platform, and buys nothing on the amd64 fleet most services run on today.
## The signature ``` func WithDataIndependentTiming(f func()) ``` One argument, no return value. You pass a closure; it runs. What surrounds that call is the interesting part. ## What the mode is "Constant-time" source code is a claim about an *algorithm*: no branch and no memory access depends on a secret. It is not automatically a claim about the *silicon*. Some processors implement some instructions with data-dependent timing — early-exit multipliers are the classic example — so an algorithm that looks branch-free at the Go level can still take a different number of cycles for different operands. Arm64 addresses this with an architectural feature, FEAT_DIT, and a processor state bit, `PSTATE.DIT`. With it set, the instructions the architecture covers are guaranteed to take a data-independent number of cycles. `subtle.WithDataIndependentTiming` is Go's way to set that bit around a region of your own code: if the running CPU supports it, the function enables the mode, runs `f`, and disables it again. ## On everything else, it is a plain call On architectures without the feature the implementation simply calls `f` and returns. There is no panic, no error, no software emulation, and no build tag you need. That is a deliberate design choice: the call is safe to write unconditionally, so portable code can request the protection without branching on `runtime.GOARCH`. The flip side is that on the amd64 fleet most services actually run on, the call buys nothing today, and a reviewer is entitled to ask whether the extra closure earns its place. ## The caveat that the documentation states outright "WithDataIndependentTiming does not make variable-time code constant-time." If the closure calls `bytes.Equal`, the early exit at the first differing byte is still there, and no processor mode removes it — the difference is in how many *instructions* execute, not in how long each one takes. This is a backstop underneath `subtle.ConstantTimeCompare`, not a replacement for it. A candidate who says "wrap the comparison in this and you can use any comparison" has the relationship exactly backwards. ## Scope, inheritance and nesting Three behaviours are worth knowing because they decide where you put the call: - **Goroutines inherit it.** Any goroutine spawned by `f`, and any goroutine those spawn, run with the mode enabled for their whole lifetime. That is convenient for a worker that `f` starts and alarming if `f` starts something long-lived, which is an argument for wrapping a small comparison rather than a whole request handler. - **cgo inherits it.** C code called through cgo from within `f` also runs with the mode enabled, and if that C code disables it, the mode is re-enabled on return to Go. - **Nesting is allowed.** An inner call notices that the mode is already on and leaves it on when it returns, so composing two functions that each wrap their own critical section behaves correctly. The disable happens in a deferred function, which means a panic inside `f` still restores the previous state as the stack unwinds — it will not leak into whatever recovers further up. ## Where it belongs in a codebase Scope it as tightly as the sensitive operation. Wrapping one comparison or one key-schedule step is the intended use. Wrapping an HTTP handler pulls every goroutine that handler starts into the mode for as long as those goroutines live, which is both broader than you meant and harder to reason about later. And the ordinary answer for comparing an authentication tag remains `hmac.Equal` or `subtle.ConstantTimeCompare`: this function adds a layer beneath a correct comparison, it does not choose one for you.
- If the code inside is already constant-time, why enable the mode at all?Because constant-time source is a claim about the algorithm, not about the silicon. Some instructions on some cores take a data-dependent number of cycles, and the mode removes that class of variation for the instructions the architecture covers. It is defence in depth beneath a function you already wrote carefully, not a first line.
- Is it a replacement for subtle.ConstantTimeCompare?No, and the documentation says so: it does not make variable-time code constant-time. If the closure calls `bytes.Equal`, the early exit at the first differing byte is untouched, because the mode changes how long instructions take, not how many of them run. Use a constant-time comparison inside, and this around it if you want the extra layer.
- What happens if f panics inside subtle.WithDataIndependentTiming?The mode is disabled from a deferred function, so it is restored while the panic unwinds and does not leak into whatever recovers further up the stack. The deferred code also only disables the mode if this call was the one that enabled it, which is what makes nested calls safe to compose.
saying these in an interview costs you the question
- Thinks it makes any code inside the closure constant-time
- Believes it panics on architectures without the feature
- Says it removes the need for subtle.ConstantTimeCompare
- Assumes goroutines started inside do not inherit the mode
- Calls it a compiler flag rather than a run-time call