Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
MISE_NODE_COREPACK = true

[tools]
node = "20"
node = "20.19"

[tasks.deps]
description = "Install all JS dependencies"
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ yarn workspace @lightsparkdev/ui test
- Use workspace protocol for internal deps: `"@lightsparkdev/ui": "*"`
- Shared configs: `@lightsparkdev/{tsconfig,eslint-config}`

### Enums
Prefer generated TypeScript enums from `src/generated/graphql` rather than raw strings when
available. This ensures type safety and keeps code in sync with the schema.

### GraphQL
After Python schema changes:
```bash
Expand Down
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@
"ts-prune": "^0.10.3",
"turbo": "^2.5.4"
},
"dependenciesMeta": {
"@central-icons-react/round-filled-radius-3-stroke-1.5": {
"built": false
},
"@central-icons-react/round-outlined-radius-0-stroke-1.5": {
"built": false
},
"@central-icons-react/round-outlined-radius-3-stroke-1.5": {
"built": false
}
},
"engines": {
"node": ">=18"
},
Expand Down
7 changes: 2 additions & 5 deletions packages/core/src/crypto/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type CryptoInterface = {
format: "pkcs8" | "spki",
) => Promise<ArrayBuffer>;

getNonce: () => Promise<number>;
getNonce: () => Promise<bigint>;

sign: (
keyOrAlias: CryptoKey | string,
Expand Down Expand Up @@ -229,10 +229,7 @@ const serializeSigningKey = async (
const getNonce = async () => {
const nonceSt = await getRandomValues32(new Uint32Array(2));
const [upper, lower] = nonceSt;
const nonce = (BigInt(upper) << 32n) | BigInt(lower);
// Note: We lose some precision here going from bigint to number
// because js numbers are floats, but it's ok.
return Number(nonce);
return (BigInt(upper) << 32n) | BigInt(lower);
};

const sign = async (
Expand Down
20 changes: 19 additions & 1 deletion packages/core/src/crypto/tests/crypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ describe("Crypto tests", () => {

test("should generate a valid nonce", async () => {
const nonce = await DefaultCrypto.getNonce();
expect(nonce).toBeGreaterThan(0);
expect(nonce > 0n).toBe(true);
}, 10_000);

test("should generate nonces that exceed Number.MAX_SAFE_INTEGER without precision loss", async () => {
const nonces = await Promise.all(
Array.from({ length: 100 }, () => DefaultCrypto.getNonce()),
);
const maxSafeInteger = BigInt(Number.MAX_SAFE_INTEGER);

for (const nonce of nonces) {
expect(typeof nonce).toBe("bigint");
// A 64-bit nonce converted to Number and back will lose precision if it
// exceeds MAX_SAFE_INTEGER. Verify the round-trip is lossless:
expect(BigInt(nonce.toString())).toBe(nonce);
}
// With 64 bits, at least some nonces should exceed MAX_SAFE_INTEGER. The
// probability of all 100 fitting in 53 bits is negligible (~2^-1100).
const hasLargeNonce = nonces.some((n: bigint) => n > maxSafeInteger);
expect(hasLargeNonce).toBe(true);
}, 10_000);
});
2 changes: 1 addition & 1 deletion packages/core/src/requester/Requester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ class Requester {
query,
variables,
operationName,
nonce,
nonce: nonce.toString(),
expires_at: expiration,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe("DefaultRequester", () => {
}),
),
serializeSigningKey: jest.fn(() => Promise.resolve(new ArrayBuffer(0))),
getNonce: jest.fn(() => Promise.resolve(123)),
getNonce: jest.fn(() => Promise.resolve(123n)),
sign: jest.fn(() => Promise.resolve(new ArrayBuffer(0))),
importPrivateSigningKey: jest.fn(() => Promise.resolve("")),
} satisfies CryptoInterface;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/requester/tests/Requester.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe("Requester", () => {
}),
),
serializeSigningKey: jest.fn(() => Promise.resolve(new ArrayBuffer(0))),
getNonce: jest.fn(() => Promise.resolve(123)),
getNonce: jest.fn(() => Promise.resolve(123n)),
sign: jest.fn(() => Promise.resolve(new ArrayBuffer(0))),
importPrivateSigningKey: jest.fn(() => Promise.resolve("")),
} satisfies CryptoInterface;
Expand Down
Loading
Loading