You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b4f2821
Browse filesBrowse the repository at this point in the historyBrowse files
* fix(auth): server-issued SIWN nonces + skew-tolerant freshness + distinct verify error codes
A device clock even slightly ahead of the server failed sign-in
deterministically across every wallet: the nonce embedded the client's
Date.now() and the server rejected any future timestamp (age < 0).
- signWithWallet() now fetches the nonce from /near/nonce (server
clock), falling back to a local nonce if the endpoint is unavailable;
accountId is optional on the nonce request so fresh-connect flows can
get a nonce before the wallet account is known
- nonce freshness tolerates up to 60s of future skew on both verify
paths (ed25519 via near-kit's nonceValidation: "none" + local check,
ML-DSA-65 in the shared check)
- verify failures return distinct codes (UNAUTHORIZED_NONCE_EXPIRED,
UNAUTHORIZED_NONCE_FROM_FUTURE, UNAUTHORIZED_SIGNATURE_INVALID,
UNAUTHORIZED_PUBLIC_KEY_INVALID, UNAUTHORIZED_ACCESS_KEY_NOT_FOUND,
UNAUTHORIZED_NOT_FULL_ACCESS_KEY, VERIFY_RPC_ERROR) instead of one
blanket "Invalid signature"; RPC failures on the access-key lookup no
longer masquerade as bad signatures
- client propagates error codes to onError; login page maps
NONCE_FROM_FUTURE to a "device clock ahead" message (4 locales)
* test(auth): sandbox integration tests for real quantum-key sign-in
Full SIWN flow against a local nearcore sandbox (near-kit Sandbox,
nearcore 2.13.4): the test provisions real accounts via AddKey
transactions carrying an ML-DSA-65 full-access key, points the plugin's
rpcUrl at the sandbox node, and drives /near/verify end to end with
zero mocked crypto — real borsh serialization, real SHA-256, real
ml_dsa65 signatures, real on-chain access-key lookups.
Covers: ml-dsa-65 sign-in happy path, nonce replay detection, tampered
signature (UNAUTHORIZED_SIGNATURE_INVALID), key-not-on-account
(UNAUTHORIZED_ACCESS_KEY_NOT_FOUND), stale nonce
(UNAUTHORIZED_NONCE_EXPIRED), and an ed25519 parity path. Skips cleanly
when the sandbox binary is unavailable.
* fix(auth): true borsh Option encoding in ML-DSA-65 NEP-413 verification
The ML-DSA-65 verifier serialized callback_url as a u32 (4 zero bytes
when absent), but borsh Option<T> is a single discriminant byte — so
every signature from a real quantum-capable wallet (Rust/borsh side,
e.g. Intear) failed verification with "Invalid signature" even though
key, nonce, and message all matched. The ed25519 path never hit this
because near-kit serializes the Option correctly.
Proven against a captured failing request from a real wallet:
ml_dsa65.verify flips false→true with the 1-byte encoding, same key and
signature. Test helpers now sign with the spec encoding, and the sandbox
integration test signs without callbackUrl while the request includes
it, exercising the callbackUrl retry against the real chain.
* test(auth): sandbox E2E for the gasless flow — quantum login, gas keys, relay
Full gasless-write chain against a local nearcore sandbox: quantum SIWN
login, gas-key bootstrap signed by the ML-DSA parent (KeyType 2
transaction accepted on-chain — proves noble/aws-lc interop for
transaction signatures), sponsor funding through /near/gas-key/fund
(real transferToGasKey), a gasless write signed by the gas key on its
own nonce lane with gas paid from the prepaid balance, and a
quantum-signed delegate relayed through /near/relay and executed by the
chain.
The receiver contract is a hand-built minimal wasm module exporting
__fastdata_kv, so the suite needs no external fixtures.
Also aligns relayedTransaction test-schema optionality with the
production migration: gasUsed and updatedAt are nullable there, but the
test DDL inferred NOT NULL, breaking any relay-route create under test.
* ci: cache the near-sandbox binary for better-near-auth sandbox tests
The sandbox integration tests boot a local nearcore node via near-kit,
which downloads the ~30MB near-sandbox binary to ~/.near-kit/sandbox/bin
on first use. Cache it keyed on the near-kit version (the binary version
source) with a restore fallback so version bumps only pay for the new
download, not the whole directory.
Fix deterministic sign-in failures caused by client/server clock skew, and make SIWN verification failures diagnosable.
6
+
7
+
ML-DSA-65 NEP-413 verification now uses correct borsh serialization for `callback_url: Option<String>` (one discriminant byte, Some followed by [u32 len][bytes]). The previous encoding wrote the Option as a u32, so signatures from real quantum-capable wallets (Rust/borsh — e.g. Intear) failed verification with "Invalid signature" even when the key, nonce, and message all matched. Test helpers now sign with the spec encoding, and the sandbox integration test signs without callbackUrl while the request includes it, matching real wallet behavior.
8
+
9
+
New sandbox integration tests run the full quantum flow against a local nearcore sandbox node (near-kit's `Sandbox`, nearcore 2.13.4, skipped when the binary is unavailable): quantum-signed SIWN login, session gas-key bootstrap signed by the ML-DSA parent key, sponsor funding via `/near/gas-key/fund`, a gasless write signed by the gas key on its own nonce lane, and a quantum-signed delegate relayed through `/near/relay`. Also aligns the `relayedTransaction` test schema with the production migration (`gasUsed`/`updatedAt` nullable).
10
+
11
+
Sign-in nonces are now issued by the server (`/near/nonce`, server clock) instead of being generated from the user's device clock, so a device clock ahead of the server no longer fails login outright; the client falls back to a local nonce if the endpoint is unavailable. `accountId` on the nonce request is now optional (omit it to get a nonce before the wallet account is known). Nonce freshness validation tolerates up to 60s of future skew (previously any future timestamp was rejected), applied to both the ed25519 (via `nonceValidation: "none"` + local check) and ML-DSA-65 paths. Verify failures now return distinct error codes — `UNAUTHORIZED_NONCE_EXPIRED`, `UNAUTHORIZED_NONCE_FROM_FUTURE`, `UNAUTHORIZED_SIGNATURE_INVALID`, `UNAUTHORIZED_PUBLIC_KEY_INVALID`, `UNAUTHORIZED_ACCESS_KEY_NOT_FOUND`, `UNAUTHORIZED_NOT_FULL_ACCESS_KEY`, `VERIFY_RPC_ERROR` — instead of a single blanket "Invalid signature", and the client propagates those codes to `onError` callbacks.
@@ -420,7 +427,8 @@ The plugin detects the network from the account ID:
420
427
## Security
421
428
422
429
### NEP-413 Compliance
423
-
- Proper nonce handling prevents replay attacks
430
+
- Nonces are issued by the server (`/near/nonce`, server clock) during sign-in, so client/server clock skew cannot fail login; the client falls back to a local nonce if the endpoint is unavailable
431
+
- Nonce freshness tolerates up to 60s of future clock skew; older timestamps are rejected with `UNAUTHORIZED_NONCE_EXPIRED`, future timestamps beyond the tolerance with `UNAUTHORIZED_NONCE_FROM_FUTURE`
424
432
- Message format and recipient validation
425
433
- 15-minute server-side nonce expiration with DB replay detection
426
434
@@ -436,7 +444,7 @@ The plugin detects the network from the account ID:
436
444
- Configurable validation for limited access keys
437
445
438
446
### Post-Quantum Keys
439
-
-`ml-dsa-65:` (FIPS 204) full-access keys are accepted on `/api/auth/near/verify` and `/api/auth/near/link-account`. Signature verification is delegated to `@noble/post-quantum`'s `ml_dsa65.verify`, while the on-chain access-key check stays the same as ed25519. The NEP-413 SHA-256 + borsh payload format is unchanged from the ed25519 path.
447
+
-`ml-dsa-65:` (FIPS 204) full-access keys are accepted on `/api/auth/near/verify` and `/api/auth/near/link-account`. Signature verification is delegated to `@noble/post-quantum`'s `ml_dsa65.verify`, while the on-chain access-key check stays the same as ed25519. The NEP-413 payload uses true borsh serialization (1-byte Option discriminant for `callback_url`), matching Rust wallets.
0 commit comments