constant-time-check
SkillFiles & storageAudit code for timing side-channels — variable-time operations on secret values. Use when the user asks to check constant-time, timing, or side-channel safety of a file, function, or diff in this repo.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the constant-time-check skill
What this skill tells your AI
The instructions your AI receives, as published by bronlabs/bron-crypto in .claude/skills/constant-time-check/SKILL.md and read by ahel’s review.
You are checking whether code introduces timing leaks on secret data. Be specific, cite file:line, and don't fix — report.
Library context (read once)
README.md/SECURITY.md: this library is not fully side-channel resistant. The CT foundation is inpkg/base/curves/**/impl/(fiat-crypto),pkg/base/cgo/boring/(BoringSSL), andpkg/base/nt/numct/(saferith/BoringSSL wrapper). Higher protocols are built on top.- Timing attacks are out of scope for the bug bounty (
SECURITY.md), but new timing leaks in otherwise constant-time packages are considered as a vulnerability. Treat them as findings.
What counts as "secret"
Anything that, if leaked bit-by-bit, breaks the protocol. In this repo:
- Private keys, signing nonces (
k), additive / Shamir shares, blinding factors. - Pedersen / paillier / ZK witnesses
- Trapdoors (
λ,ϕ(N), RSA primesp/q) - Encryption ephemerals, Diffie–Hellman secrets
- Decrypted plaintext where confidentiality matters If you're unsure whether a value is secret, say so in the finding rather than silently exclude it.
Steps
- Identify the target (file / package / diff). Determine audit scope: is it reachable from
pkg/mpc/signatures/**or transitively depended on by it? In-scope code gets the higher bar. - List the secret values in the function. For each one, check the patterns below.
- Output findings as a flat list with severity.
Patterns to flag (with the right replacement)
Branching on secrets
if secret == 0 { … },switch secret { … }, ternary-likeif cond { x } else { y }whereconddepends on a secret.- Loops with secret-dependent length or early exit:
for secret > 0,for i, b := range secretBytes { if b != 0 { break } }. - Use:
pkg/base/ct—ct.IsZero,ct.Equal,ct.Less[OrEqual],ct.Greater[OrEqual],ct.Choice,ct.CSelectInt,ct.CMOVInt,ct.CSwapInt. For byte/slice ops:ct.SliceEqual,ct.SliceIsZero,ct.CSelectInts.
Variable-time big-integer arithmetic on secrets
math/big.Int,math/big.NewInt,math/big.Exp,Mod,ModInverse,Cmpare all variable time. Same forpkg/base/nt/num(the variable-time signed-int wrapper).- Use:
pkg/base/nt/numct(constant-timeNat/Modulus/Int) for any value that holds or is derived from a secret. Particularlynumct.Modulus.Exp,numct.Nat.ModInverse,numct.Nat.Mod. - Modular exponentiation with a secret exponent must be CT and (where the modulus has unknown order) blinded. Check the call site uses
numctormodularand notnum/math/big.
Equality on byte buffers
bytes.Equal(a, b),==on[N]byte,string(a) == string(b)— variable-time.- Use:
crypto/subtle.ConstantTimeCompare(stdlib, fixed-length) orpkg/base/ct.SliceEqual(any length, returnsChoice).
Table / array lookup by a secret index
table[secretIndex],arr[secretByte],slice[secret % len]— leaks via cache.- Use: linear scan with
ct.CSelect…to fold all entries.
Conditional negate / select / swap implemented as if
if neg { x = x.Neg() },if cond { swap(a, b) }.- Use:
ct.ConditionallyNegatableinterface implementations (numct.Intprovides one) orct.CMOVInt/ct.CSwapInt.
Secret-dependent allocation or length
- If length is secret, ensure the secret is not variable-length.
- Pad to a fixed length, or work in-place on a fixed-size buffer.
Logging / formatting / Stringer on secrets
fmt.Print*(secret),slog.Info(... "key", secret),secret.String().- Secret types should override
String()to redact (e.g., return"<redacted>"), and not satisfyfmt.Stringeraccidentally via a wrapped field.
Hash / KDF inputs whose length is secret
- Feeding
len(secret)bytes into a sponge / merkle-damgård hash leaks the length via timing of the absorb loop. Use a fixed-size canonical encoding.
Loops over public-and-secret zipped data
for i := range pub { if pub[i] != secret[i] { return false } }— leaks the index of the first difference.- Use:
ct.SliceEqualor accumulate XORs and compare to zero withct.IsZeroat the end.
Calls into variable-time primitives
crypto/elliptic(the deprecated curve API) is variable-time on most curves, Instead Use: this repo's curve packages (pkg/base/curves/k256,…/edwards25519,…/p256, etc.) which build on fiat-crypto.
What is acceptable
- Branching on public values (group order
n, public keyP, public messagem, hash output of public data). - Early exit when the secret has already been spent / committed (e.g., after the response
sis published). if err != nil { … }—erris not secret as long as the error doesn't encode a secret/sensitive information about the secret.
Output format
Output as GitHub-flavoured markdown. Use headers, bold severity tags, and code spans for paths and replacements so the terminal renders a clear visual hierarchy. Don't wrap the whole report in a fenced code block. Template:
## Constant-time check — <target>
**Scope:** in-scope — _or_ — experimental
**Secrets identified:** `secret1`, `secret2`, …
### Findings
- **[critical]** `file.go:42` — short description
- _Replace with:_ `ct.Foo`
- **[major]** `file.go:99` — short description
- _Replace with:_ `numct.Bar`
- **[minor]** `file.go:120` — short description
- _Replace with:_ ...
- **[question]** `file.go:150` — value may be secret; needs caller-side confirmation
_(If none: "No findings.")_
### Summary
**Counts:** N critical, N major, N minor, N question.
Severity tags must be one of [critical], [major], [minor], [question] and always bolded. Order findings by severity (critical first), then by file path.
Don'ts
- Don't claim a value is constant-time without verifying the underlying implementation. If you're unsure whether
numct.Nat.Foois CT, say so and link to the source line. - Don't suggest "just remove the branch" without giving a CT replacement.
- Don't edit the code — this is an audit, not a fix.
Signals
- GitHub stars
- 20
- Forks
- 4
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
constant-time-check- Source
- github.com/bronlabs/bron-crypto