hpyhacking/peatio · error · APIv2::InvalidTonceError
2007
2007
Error message
The tonce #{tonce} is invalid, current timestamp is #{now}. What it means
Error code 2007 (InvalidTonceError) is raised by the window check in APIv2::Auth::Authenticator#check_tonce!: tonce must satisfy now-30000 <= tonce <= now+30000, where now is the server's Time.now in milliseconds. tonce is read with params[:tonce].to_i, so a seconds-precision timestamp (10 digits instead of 13) is always far below the window and rejected regardless of clock accuracy.
Source
Thrown at app/api/api_v2/auth/authenticator.rb:46
def check_signature!
if @params[:signature] != Utils.hmac_signature(token.secret_key, payload)
Rails.logger.warn "APIv2 auth failed: signature doesn't match. token: #{token.access_key} payload: #{payload}"
raise IncorrectSignatureError, @params[:signature]
end
end
def check_tonce!
key = "api_v2:tonce:#{token.access_key}:#{tonce}"
if Utils.cache.read(key)
Rails.logger.warn "APIv2 auth failed: used tonce. token: #{token.access_key} payload: #{payload} tonce: #{tonce}"
raise TonceUsedError.new(token.access_key, tonce)
end
Utils.cache.write key, tonce, 61 # forget after 61 seconds
now = Time.now.to_i*1000
if tonce < now-30000 || tonce > now+30000 # within 30 seconds
Rails.logger.warn "APIv2 auth failed: invalid tonce. token: #{token.access_key} payload: #{payload} tonce: #{tonce} current timestamp: #{now}"
raise InvalidTonceError.new(tonce, now)
end
end
def tonce
@tonce ||= @params[:tonce].to_i
end
def payload
"#{canonical_verb}|#{APIv2::Mount::PREFIX}#{canonical_uri}|#{canonical_query}"
end
def canonical_verb
@request.request_method
end
def canonical_uri
@request.path_info
endView on GitHub (pinned to dab8641137)
Solutions
- Send tonce as epoch milliseconds (Time.now.to_i * 1000) and transmit the request immediately after signing
- Sync the machine clock (enable NTP/chrony; restart drifted Docker VMs)
- If skew cannot be fixed, derive tonce from the server: GET /api/v2/timestamp returns server time in seconds — multiply by 1000 and add a counter for sub-millisecond bursts
Example fix
# before — seconds precision, local clock
params[:tonce] = Time.now.to_i
# after — server-derived milliseconds
server_ms = JSON.parse(HTTP.get('https://host/api/v2/timestamp').body).to_i * 1000
params[:tonce] = server_ms + sequence_counter Defensive patterns
Strategy: validation
Validate before calling
# Before signing, confirm local time is inside the server's +/-30s window
server_now = JSON.parse(HTTP.get('https://host/api/v2/timestamp').body) * 1000
skew = Time.now.to_i * 1000 - server_now
raise "clock skew #{skew}ms exceeds 30s — sync NTP or use server time" if skew.abs > 25_000
params[:tonce] = server_now Try / catch
The 2007 message includes the server's current millisecond timestamp ('current timestamp is <now>'); on catch, compute offset = server_now - tonce_sent, correct the client's tonce base, then resend with a fresh tonce and signature once, instead of blind-retrying. Prevention
- Run NTP/chrony on hosts and CI runners that sign requests
- Use milliseconds, never seconds, for tonce
- Fire the request immediately after signing; never sign requests ahead of time into a queue
- Prefer server time (GET /api/v2/timestamp) as the tonce source when client clocks are unreliable
When it happens
Trigger: Client clock skewed more than 30 seconds from the server; tonce sent in seconds instead of milliseconds; a signed request delayed or queued more than 30 seconds between tonce generation and delivery; container/VM clock drift.
Common situations: Laptop slept and resumed with a stale clock; Docker Desktop or VM host clock drift without NTP; offline or air-gapped CI runners; porting a client from an API that expects seconds.
Related errors
AI-assisted analysis of hpyhacking/peatio@dab8641137 (2026-08-23).
Data as JSON: /api/errors/9ae98b08dcfd1951.
Report an issue: GitHub.