@t-0/provider-sdk 1.1.28 → 1.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +52 -2
  2. package/lib/cjs/client/client.d.ts +4 -14
  3. package/lib/cjs/client/client.js +2 -38
  4. package/lib/cjs/client/signer.d.ts +1 -3
  5. package/lib/cjs/client/signer.js +7 -34
  6. package/lib/cjs/common/client/client.d.ts +14 -0
  7. package/lib/cjs/common/client/client.js +42 -0
  8. package/lib/cjs/common/client/signer.d.ts +3 -0
  9. package/lib/cjs/common/client/signer.js +36 -0
  10. package/lib/cjs/common/crypto/hash.d.ts +2 -0
  11. package/lib/cjs/common/crypto/hash.js +17 -0
  12. package/lib/cjs/common/crypto/index.d.ts +5 -0
  13. package/lib/cjs/common/crypto/index.js +14 -0
  14. package/lib/cjs/common/crypto/keys.d.ts +2 -0
  15. package/lib/cjs/common/crypto/keys.js +23 -0
  16. package/lib/cjs/common/crypto/request.d.ts +20 -0
  17. package/lib/cjs/common/crypto/request.js +51 -0
  18. package/lib/cjs/common/crypto/verify.d.ts +1 -0
  19. package/lib/cjs/common/crypto/verify.js +19 -0
  20. package/lib/cjs/common/health.d.ts +21 -0
  21. package/lib/cjs/common/health.js +45 -0
  22. package/lib/cjs/common/health_pb.d.ts +157 -0
  23. package/lib/cjs/common/health_pb.js +90 -0
  24. package/lib/cjs/common/node.d.ts +3 -0
  25. package/lib/cjs/common/node.js +15 -0
  26. package/lib/cjs/common/service.d.ts +37 -0
  27. package/lib/cjs/common/service.js +85 -0
  28. package/lib/cjs/common/validate.d.ts +25 -0
  29. package/lib/cjs/common/validate.js +42 -0
  30. package/lib/cjs/common/validation.d.ts +26 -0
  31. package/lib/cjs/common/validation.js +57 -0
  32. package/lib/cjs/crypto/hash.d.ts +1 -0
  33. package/lib/cjs/crypto/hash.js +17 -0
  34. package/lib/cjs/crypto/index.d.ts +1 -0
  35. package/lib/cjs/crypto/index.js +17 -0
  36. package/lib/cjs/crypto/keys.d.ts +1 -0
  37. package/lib/cjs/crypto/keys.js +17 -0
  38. package/lib/cjs/crypto/request.d.ts +1 -0
  39. package/lib/cjs/crypto/request.js +17 -0
  40. package/lib/cjs/crypto/verify.d.ts +1 -0
  41. package/lib/cjs/crypto/verify.js +17 -0
  42. package/lib/cjs/index.d.ts +4 -0
  43. package/lib/cjs/index.js +9 -1
  44. package/lib/cjs/service/health.d.ts +4 -21
  45. package/lib/cjs/service/health.js +5 -41
  46. package/lib/cjs/service/health_pb.d.ts +1 -157
  47. package/lib/cjs/service/health_pb.js +15 -85
  48. package/lib/cjs/service/node.d.ts +1 -3
  49. package/lib/cjs/service/node.js +15 -13
  50. package/lib/cjs/service/service.d.ts +7 -10
  51. package/lib/cjs/service/service.js +5 -94
  52. package/lib/cjs/service/validate.d.ts +1 -0
  53. package/lib/cjs/service/validate.js +17 -0
  54. package/lib/cjs/service/validate_response.d.ts +11 -6
  55. package/lib/cjs/service/validate_response.js +19 -30
  56. package/lib/cjs/version.d.ts +1 -1
  57. package/lib/cjs/version.js +1 -1
  58. package/lib/esm/client/client.d.ts +3 -13
  59. package/lib/esm/client/client.js +2 -35
  60. package/lib/esm/client/signer.d.ts +1 -3
  61. package/lib/esm/client/signer.js +1 -32
  62. package/lib/esm/common/client/client.d.ts +14 -0
  63. package/lib/esm/common/client/client.js +36 -0
  64. package/lib/esm/common/client/signer.d.ts +3 -0
  65. package/lib/esm/common/client/signer.js +32 -0
  66. package/lib/esm/common/crypto/hash.d.ts +2 -0
  67. package/lib/esm/common/crypto/hash.js +13 -0
  68. package/lib/esm/common/crypto/index.d.ts +5 -0
  69. package/lib/esm/common/crypto/index.js +4 -0
  70. package/lib/esm/common/crypto/keys.d.ts +2 -0
  71. package/lib/esm/common/crypto/keys.js +19 -0
  72. package/lib/esm/common/crypto/request.d.ts +20 -0
  73. package/lib/esm/common/crypto/request.js +47 -0
  74. package/lib/esm/common/crypto/verify.d.ts +1 -0
  75. package/lib/esm/common/crypto/verify.js +16 -0
  76. package/lib/esm/common/health.d.ts +21 -0
  77. package/lib/esm/common/health.js +41 -0
  78. package/lib/esm/common/health_pb.d.ts +157 -0
  79. package/lib/esm/common/health_pb.js +87 -0
  80. package/lib/esm/common/node.d.ts +3 -0
  81. package/lib/esm/common/node.js +11 -0
  82. package/lib/esm/common/service.d.ts +37 -0
  83. package/lib/esm/common/service.js +78 -0
  84. package/lib/esm/common/validate.d.ts +25 -0
  85. package/lib/esm/common/validate.js +38 -0
  86. package/lib/esm/common/validation.d.ts +26 -0
  87. package/lib/esm/common/validation.js +54 -0
  88. package/lib/esm/crypto/hash.d.ts +1 -0
  89. package/lib/esm/crypto/hash.js +1 -0
  90. package/lib/esm/crypto/index.d.ts +1 -0
  91. package/lib/esm/crypto/index.js +1 -0
  92. package/lib/esm/crypto/keys.d.ts +1 -0
  93. package/lib/esm/crypto/keys.js +1 -0
  94. package/lib/esm/crypto/request.d.ts +1 -0
  95. package/lib/esm/crypto/request.js +1 -0
  96. package/lib/esm/crypto/verify.d.ts +1 -0
  97. package/lib/esm/crypto/verify.js +1 -0
  98. package/lib/esm/index.d.ts +4 -0
  99. package/lib/esm/index.js +4 -0
  100. package/lib/esm/service/health.d.ts +4 -21
  101. package/lib/esm/service/health.js +3 -41
  102. package/lib/esm/service/health_pb.d.ts +1 -157
  103. package/lib/esm/service/health_pb.js +1 -84
  104. package/lib/esm/service/node.d.ts +1 -3
  105. package/lib/esm/service/node.js +1 -11
  106. package/lib/esm/service/service.d.ts +7 -10
  107. package/lib/esm/service/service.js +4 -91
  108. package/lib/esm/service/validate.d.ts +1 -0
  109. package/lib/esm/service/validate.js +1 -0
  110. package/lib/esm/service/validate_response.d.ts +11 -6
  111. package/lib/esm/service/validate_response.js +15 -27
  112. package/lib/esm/version.d.ts +1 -1
  113. package/lib/esm/version.js +1 -1
  114. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  115. package/lib/tsconfig.esm.tsbuildinfo +1 -1
  116. package/package.json +15 -5
@@ -1,38 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createResponseValidation = void 0;
4
- exports.createValidationInterceptor = createValidationInterceptor;
5
- const connect_1 = require("@connectrpc/connect");
6
- const protovalidate_1 = require("@bufbuild/protovalidate");
7
- const validate_1 = require("@connectrpc/validate");
8
- const validator = (0, protovalidate_1.createValidator)();
3
+ exports.createResponseValidation = exports.networkRegistry = exports.createValidationInterceptor = void 0;
4
+ exports.createNetworkValidationInterceptor = createNetworkValidationInterceptor;
5
+ const protobuf_1 = require("@bufbuild/protobuf");
6
+ const provider_pb_js_1 = require("../common/gen/tzero/v1/payment/provider_pb.js");
7
+ const network_pb_js_1 = require("../common/gen/tzero/v1/payment/network_pb.js");
8
+ const validation_js_1 = require("../common/validation.js");
9
+ // Re-export everything from the common layer for backward compatibility.
10
+ var validation_js_2 = require("../common/validation.js");
11
+ Object.defineProperty(exports, "createValidationInterceptor", { enumerable: true, get: function () { return validation_js_2.createValidationInterceptor; } });
9
12
  /**
10
- * Creates a ConnectRPC interceptor that validates provider responses against
11
- * buf.validate proto annotations before they are serialized and sent.
12
- * Also validates incoming requests using the official @connectrpc/validate interceptor.
13
- *
14
- * Invalid requests return Code.InvalidArgument; invalid responses return Code.Internal.
13
+ * Registry covering the t-0 network provider contract protos. Leaf file
14
+ * descriptors pull in their full dependency graph, so adding new messages
15
+ * or fields to existing protos requires only `buf:generate`.
15
16
  */
16
- function createValidationInterceptor() {
17
- const requestInterceptor = (0, validate_1.createValidateInterceptor)();
18
- return (next) => async (req) => {
19
- // Validate request (delegates to official interceptor which throws on invalid)
20
- const resp = await requestInterceptor(next)(req);
21
- // Validate response
22
- const schema = req.method.output;
23
- const msg = resp.message;
24
- const result = validator.validate(schema, msg);
25
- if (result.kind === "invalid") {
26
- const details = result.violations.map(v => `${v.field?.toString() ?? ""}: ${v.message}`).join("; ");
27
- throw new connect_1.ConnectError(`response validation failed: ${details}`, connect_1.Code.Internal);
28
- }
29
- if (result.kind === "error") {
30
- throw new connect_1.ConnectError(`response validation error: ${result.error.message}`, connect_1.Code.Internal);
31
- }
32
- return resp;
33
- };
17
+ exports.networkRegistry = (0, protobuf_1.createRegistry)(provider_pb_js_1.file_tzero_v1_payment_provider, network_pb_js_1.file_tzero_v1_payment_network);
18
+ /**
19
+ * Validation interceptor pre-configured for the t-0 network provider contract.
20
+ */
21
+ function createNetworkValidationInterceptor(logger) {
22
+ return (0, validation_js_1.createValidationInterceptor)({ logger, registry: exports.networkRegistry });
34
23
  }
35
24
  /**
36
25
  * @deprecated Use createValidationInterceptor instead.
37
26
  */
38
- exports.createResponseValidation = createValidationInterceptor;
27
+ exports.createResponseValidation = validation_js_1.createValidationInterceptor;
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "1.1.28";
1
+ export declare const SDK_VERSION = "1.1.30";
@@ -3,4 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SDK_VERSION = void 0;
4
4
  // SDK semantic version. Bumped in lockstep with all other SDKs by the
5
5
  // release.yaml workflow.
6
- exports.SDK_VERSION = "1.1.28";
6
+ exports.SDK_VERSION = "1.1.30";
@@ -1,16 +1,6 @@
1
- import { DescService } from "@bufbuild/protobuf";
1
+ import { type Signature } from "../common/client/client.js";
2
+ import type { DescService } from "@bufbuild/protobuf";
2
3
  export declare const DEFAULT_ENDPOINT = "https://api.t-0.network";
3
4
  export declare function createClient<T extends DescService>(signer: string | Buffer | ((data: Buffer) => Promise<Signature>) | Buffer<ArrayBufferLike>, endpoint: string | undefined, svc: T): import("@connectrpc/connect").Client<T>;
4
- /**
5
- * Signature with metadata for particular request
6
- */
7
- export interface Signature {
8
- signature: Buffer;
9
- publicKey: Buffer;
10
- }
11
- /**
12
- * Signature function for signing requests to T-0 API. Accepts any data in string format and return signature
13
- * with metadata
14
- */
15
- export type SignerFunction = (data: Buffer) => Promise<Signature>;
5
+ export type { Signature, SignerFunction } from "../common/client/client.js";
16
6
  export default createClient;
@@ -1,39 +1,6 @@
1
- import { createClient as createConnectClient } from "@connectrpc/connect";
2
- import { createConnectTransport } from "@connectrpc/connect-web";
3
- import { keccak_256 } from "@noble/hashes/sha3.js";
4
- import CreateSigner from "./signer.js";
5
- import NetworkHeaders from "../common/headers.js";
1
+ import { createClient as createClientCommon } from "../common/client/client.js";
6
2
  export const DEFAULT_ENDPOINT = "https://api.t-0.network";
7
3
  export function createClient(signer, endpoint, svc) {
8
- let customFetch;
9
- endpoint = endpoint || DEFAULT_ENDPOINT;
10
- if (typeof signer === "string" || Buffer.isBuffer(signer)) {
11
- signer = CreateSigner(signer);
12
- }
13
- customFetch = async (r, init) => {
14
- if (!init?.body || !((init.body) instanceof Uint8Array)) {
15
- throw "unsupported body type";
16
- }
17
- const ts = Date.now();
18
- // 64‑bit little‑endian timestamp
19
- const tsBuf = Buffer.alloc(8);
20
- tsBuf.writeBigUInt64LE(BigInt(ts));
21
- const hash = keccak_256.create()
22
- .update(init.body)
23
- .update(tsBuf);
24
- const hashHex = Buffer.from(hash.digest());
25
- const sig = await signer(hashHex);
26
- const headers = new Headers(init?.headers);
27
- headers.append(NetworkHeaders.Signature, "0x" + sig.signature.toString('hex'));
28
- headers.append(NetworkHeaders.PublicKey, "0x" + sig.publicKey.toString('hex'));
29
- headers.append(NetworkHeaders.SignatureTimestamp, ts.toString());
30
- const modifiedInit = { ...init, headers };
31
- return fetch(r, modifiedInit);
32
- };
33
- const transport = createConnectTransport({
34
- baseUrl: endpoint || DEFAULT_ENDPOINT,
35
- fetch: customFetch,
36
- });
37
- return createConnectClient(svc, transport);
4
+ return createClientCommon(signer, endpoint || DEFAULT_ENDPOINT, svc);
38
5
  }
39
6
  export default createClient;
@@ -1,3 +1 @@
1
- import { Signature } from "./client.js";
2
- export declare const CreateSigner: (privateKey: string | Buffer) => (data: Buffer) => Promise<Signature>;
3
- export default CreateSigner;
1
+ export { CreateSigner, default } from "../common/client/signer.js";
@@ -1,32 +1 @@
1
- import { secp256k1 } from '@noble/curves/secp256k1.js';
2
- export const CreateSigner = (privateKey) => {
3
- privateKey = parsePrivateKey(privateKey);
4
- const publicKey = Buffer.from(secp256k1.getPublicKey(privateKey, false));
5
- return async (data) => {
6
- // Ensure hash is 32 bytes
7
- if (data.length !== 32) {
8
- throw new Error('Message hash must be 32 bytes');
9
- }
10
- // Sign the hash
11
- const signature = secp256k1.sign(data, privateKey, { prehash: false });
12
- return {
13
- signature: Buffer.from(signature),
14
- publicKey: publicKey,
15
- };
16
- };
17
- };
18
- const parsePrivateKey = (privateKey) => {
19
- if (typeof privateKey == 'string') {
20
- privateKey = privateKey.replace(/^0x/, '');
21
- if (!/^[0-9a-fA-F]{64}$/.test(privateKey)) {
22
- throw new Error('Private key must be 64 hex characters');
23
- }
24
- privateKey = Buffer.from(privateKey, 'hex');
25
- }
26
- // Validate private key
27
- if (!secp256k1.utils.isValidSecretKey(privateKey)) {
28
- throw new Error('Invalid private key');
29
- }
30
- return privateKey;
31
- };
32
- export default CreateSigner;
1
+ export { CreateSigner, default } from "../common/client/signer.js";
@@ -0,0 +1,14 @@
1
+ import { DescService } from "@bufbuild/protobuf";
2
+ export declare function createClient<T extends DescService>(signer: string | Buffer | ((data: Buffer) => Promise<Signature>) | Buffer<ArrayBufferLike>, endpoint: string, svc: T): import("@connectrpc/connect").Client<T>;
3
+ /**
4
+ * Signature with metadata for particular request
5
+ */
6
+ export interface Signature {
7
+ signature: Buffer;
8
+ publicKey: Buffer;
9
+ }
10
+ /**
11
+ * Signature function for signing requests to T-0 API. Accepts any data in string format and return signature
12
+ * with metadata
13
+ */
14
+ export type SignerFunction = (data: Buffer) => Promise<Signature>;
@@ -0,0 +1,36 @@
1
+ import { createClient as createConnectClient } from "@connectrpc/connect";
2
+ import { createConnectTransport } from "@connectrpc/connect-web";
3
+ import { keccak_256 } from "@noble/hashes/sha3.js";
4
+ import CreateSigner from "./signer.js";
5
+ import NetworkHeaders from "../headers.js";
6
+ export function createClient(signer, endpoint, svc) {
7
+ let customFetch;
8
+ if (typeof signer === "string" || Buffer.isBuffer(signer)) {
9
+ signer = CreateSigner(signer);
10
+ }
11
+ customFetch = async (r, init) => {
12
+ if (!init?.body || !((init.body) instanceof Uint8Array)) {
13
+ throw "unsupported body type";
14
+ }
15
+ const ts = Date.now();
16
+ // 64‑bit little‑endian timestamp
17
+ const tsBuf = Buffer.alloc(8);
18
+ tsBuf.writeBigUInt64LE(BigInt(ts));
19
+ const hash = keccak_256.create()
20
+ .update(init.body)
21
+ .update(tsBuf);
22
+ const hashHex = Buffer.from(hash.digest());
23
+ const sig = await signer(hashHex);
24
+ const headers = new Headers(init?.headers);
25
+ headers.append(NetworkHeaders.Signature, "0x" + sig.signature.toString('hex'));
26
+ headers.append(NetworkHeaders.PublicKey, "0x" + sig.publicKey.toString('hex'));
27
+ headers.append(NetworkHeaders.SignatureTimestamp, ts.toString());
28
+ const modifiedInit = { ...init, headers };
29
+ return fetch(r, modifiedInit);
30
+ };
31
+ const transport = createConnectTransport({
32
+ baseUrl: endpoint,
33
+ fetch: customFetch,
34
+ });
35
+ return createConnectClient(svc, transport);
36
+ }
@@ -0,0 +1,3 @@
1
+ import type { Signature } from "./client.js";
2
+ export declare const CreateSigner: (privateKey: string | Buffer) => (data: Buffer) => Promise<Signature>;
3
+ export default CreateSigner;
@@ -0,0 +1,32 @@
1
+ import { secp256k1 } from '@noble/curves/secp256k1.js';
2
+ export const CreateSigner = (privateKey) => {
3
+ privateKey = parsePrivateKey(privateKey);
4
+ const publicKey = Buffer.from(secp256k1.getPublicKey(privateKey, false));
5
+ return async (data) => {
6
+ // Ensure hash is 32 bytes
7
+ if (data.length !== 32) {
8
+ throw new Error('Message hash must be 32 bytes');
9
+ }
10
+ // Sign the hash
11
+ const signature = secp256k1.sign(data, privateKey, { prehash: false });
12
+ return {
13
+ signature: Buffer.from(signature),
14
+ publicKey: publicKey,
15
+ };
16
+ };
17
+ };
18
+ const parsePrivateKey = (privateKey) => {
19
+ if (typeof privateKey == 'string') {
20
+ privateKey = privateKey.replace(/^0x/, '');
21
+ if (!/^[0-9a-fA-F]{64}$/.test(privateKey)) {
22
+ throw new Error('Private key must be 64 hex characters');
23
+ }
24
+ privateKey = Buffer.from(privateKey, 'hex');
25
+ }
26
+ // Validate private key
27
+ if (!secp256k1.utils.isValidSecretKey(privateKey)) {
28
+ throw new Error('Invalid private key');
29
+ }
30
+ return privateKey;
31
+ };
32
+ export default CreateSigner;
@@ -0,0 +1,2 @@
1
+ export declare function keccak256(...inputs: Uint8Array[]): Buffer;
2
+ export declare function computeDigest(body: Uint8Array, timestampMs: number): Buffer;
@@ -0,0 +1,13 @@
1
+ import { keccak_256 } from '@noble/hashes/sha3.js';
2
+ export function keccak256(...inputs) {
3
+ const h = keccak_256.create();
4
+ for (const input of inputs) {
5
+ h.update(input);
6
+ }
7
+ return Buffer.from(h.digest());
8
+ }
9
+ export function computeDigest(body, timestampMs) {
10
+ const tsBuf = Buffer.alloc(8);
11
+ tsBuf.writeBigUInt64LE(BigInt(timestampMs));
12
+ return keccak256(body, tsBuf);
13
+ }
@@ -0,0 +1,5 @@
1
+ export { verifySignature } from './verify.js';
2
+ export { keccak256, computeDigest } from './hash.js';
3
+ export { parsePublicKey, publicKeysEqual } from './keys.js';
4
+ export { createRequestVerifier, DEFAULT_TOLERANCE_MS } from './request.js';
5
+ export type { CreateVerifierOptions, VerifyRequest, VerifyRequestResult, VerifyRequestFailure, RequestVerifier } from './request.js';
@@ -0,0 +1,4 @@
1
+ export { verifySignature } from './verify.js';
2
+ export { keccak256, computeDigest } from './hash.js';
3
+ export { parsePublicKey, publicKeysEqual } from './keys.js';
4
+ export { createRequestVerifier, DEFAULT_TOLERANCE_MS } from './request.js';
@@ -0,0 +1,2 @@
1
+ export declare function parsePublicKey(key: string | Buffer): Buffer;
2
+ export declare function publicKeysEqual(a: Uint8Array, b: Uint8Array): boolean;
@@ -0,0 +1,19 @@
1
+ export function parsePublicKey(key) {
2
+ if (typeof key === 'string') {
3
+ const hex = key.startsWith('0x') ? key.slice(2) : key;
4
+ if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
5
+ throw new Error('Public key contains invalid hex characters');
6
+ }
7
+ key = Buffer.from(hex, 'hex');
8
+ }
9
+ if (key.length !== 65 || key[0] !== 0x04) {
10
+ throw new Error('Public key must be 65 bytes in uncompressed format (0x04 prefix)');
11
+ }
12
+ return Buffer.from(key);
13
+ }
14
+ export function publicKeysEqual(a, b) {
15
+ if (a.length !== b.length) {
16
+ return false;
17
+ }
18
+ return Buffer.from(a).compare(Buffer.from(b)) === 0;
19
+ }
@@ -0,0 +1,20 @@
1
+ export declare const DEFAULT_TOLERANCE_MS = 60000;
2
+ export interface CreateVerifierOptions {
3
+ networkPublicKey: string | Buffer;
4
+ toleranceMs?: number;
5
+ }
6
+ export interface VerifyRequest {
7
+ body: Uint8Array | ArrayBufferView | ArrayBufferLike;
8
+ signatureHeader: string;
9
+ publicKeyHeader: string;
10
+ timestampHeader: string;
11
+ }
12
+ export type VerifyRequestFailure = 'invalid_timestamp' | 'timestamp_out_of_range' | 'invalid_public_key' | 'unknown_public_key' | 'invalid_signature_format' | 'signature_failed';
13
+ export type VerifyRequestResult = {
14
+ valid: true;
15
+ } | {
16
+ valid: false;
17
+ reason: VerifyRequestFailure;
18
+ };
19
+ export type RequestVerifier = (req: VerifyRequest) => VerifyRequestResult;
20
+ export declare function createRequestVerifier(opts: CreateVerifierOptions): RequestVerifier;
@@ -0,0 +1,47 @@
1
+ import { verifySignature } from './verify.js';
2
+ import { computeDigest } from './hash.js';
3
+ import { parsePublicKey, publicKeysEqual } from './keys.js';
4
+ export const DEFAULT_TOLERANCE_MS = 60000;
5
+ export function createRequestVerifier(opts) {
6
+ const networkKey = parsePublicKey(opts.networkPublicKey);
7
+ const tolerance = opts.toleranceMs ?? DEFAULT_TOLERANCE_MS;
8
+ return (req) => {
9
+ const ts = parseInt(req.timestampHeader, 10);
10
+ if (!Number.isFinite(ts) || ts < 0) {
11
+ return { valid: false, reason: 'invalid_timestamp' };
12
+ }
13
+ if (Math.abs(Date.now() - ts) > tolerance) {
14
+ return { valid: false, reason: 'timestamp_out_of_range' };
15
+ }
16
+ let publicKey;
17
+ try {
18
+ publicKey = parsePublicKey(req.publicKeyHeader);
19
+ }
20
+ catch {
21
+ return { valid: false, reason: 'invalid_public_key' };
22
+ }
23
+ if (!publicKeysEqual(publicKey, networkKey)) {
24
+ return { valid: false, reason: 'unknown_public_key' };
25
+ }
26
+ const sigHex = req.signatureHeader.startsWith('0x')
27
+ ? req.signatureHeader.slice(2)
28
+ : req.signatureHeader;
29
+ if (!/^[0-9a-fA-F]*$/.test(sigHex) || sigHex.length % 2 !== 0) {
30
+ return { valid: false, reason: 'invalid_signature_format' };
31
+ }
32
+ const signature = Buffer.from(sigHex, 'hex');
33
+ if (signature.length !== 64 && signature.length !== 65) {
34
+ return { valid: false, reason: 'invalid_signature_format' };
35
+ }
36
+ const body = req.body instanceof Uint8Array
37
+ ? req.body
38
+ : ArrayBuffer.isView(req.body)
39
+ ? new Uint8Array(req.body.buffer, req.body.byteOffset, req.body.byteLength)
40
+ : new Uint8Array(req.body);
41
+ const digest = computeDigest(body, ts);
42
+ if (!verifySignature(publicKey, digest, signature)) {
43
+ return { valid: false, reason: 'signature_failed' };
44
+ }
45
+ return { valid: true };
46
+ };
47
+ }
@@ -0,0 +1 @@
1
+ export declare function verifySignature(publicKey: Uint8Array, digest: Uint8Array, signature: Uint8Array): boolean;
@@ -0,0 +1,16 @@
1
+ import { secp256k1 } from '@noble/curves/secp256k1.js';
2
+ export function verifySignature(publicKey, digest, signature) {
3
+ if (digest.length !== 32) {
4
+ return false;
5
+ }
6
+ if (signature.length !== 64 && signature.length !== 65) {
7
+ return false;
8
+ }
9
+ const sig64 = signature.length === 65 ? signature.subarray(0, 64) : signature;
10
+ try {
11
+ return secp256k1.verify(sig64, digest, publicKey, { prehash: false });
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
@@ -0,0 +1,21 @@
1
+ import { type ServiceImpl } from "@connectrpc/connect";
2
+ import { Health } from "./health_pb.js";
3
+ /**
4
+ * Headers carrying the identity of the SDK answering the probe. They ride on
5
+ * the health response and nowhere else: `HealthCheckResponse` has a single
6
+ * status field and `check` names its service in the request, so the contract
7
+ * itself has no room for this — and taxing every callback with headers only the
8
+ * probe reads would be worse.
9
+ */
10
+ export declare const SDK_ECOSYSTEM_HEADER = "T0-Sdk-Ecosystem";
11
+ export declare const SDK_VERSION_HEADER = "T0-Sdk-Version";
12
+ /**
13
+ * Reports SERVING for the services registered on this server and NOT_FOUND for
14
+ * anything else. The set is frozen at registration; nothing is computed per
15
+ * request.
16
+ *
17
+ * `watch` is absent on purpose: it is server-streaming, and the body-hash
18
+ * signature scheme these servers run behind has no story for streams. Omitting
19
+ * it from a partial `ServiceImpl` makes connect-es answer UNIMPLEMENTED.
20
+ */
21
+ export declare const createHealthServiceImpl: (services: string[], version?: string) => Partial<ServiceImpl<typeof Health>>;
@@ -0,0 +1,41 @@
1
+ import { create } from "@bufbuild/protobuf";
2
+ import { Code, ConnectError } from "@connectrpc/connect";
3
+ import { HealthCheckResponse_ServingStatus, HealthCheckResponseSchema, } from "./health_pb.js";
4
+ /**
5
+ * Headers carrying the identity of the SDK answering the probe. They ride on
6
+ * the health response and nowhere else: `HealthCheckResponse` has a single
7
+ * status field and `check` names its service in the request, so the contract
8
+ * itself has no room for this — and taxing every callback with headers only the
9
+ * probe reads would be worse.
10
+ */
11
+ export const SDK_ECOSYSTEM_HEADER = "T0-Sdk-Ecosystem";
12
+ export const SDK_VERSION_HEADER = "T0-Sdk-Version";
13
+ const SDK_ECOSYSTEM = "node";
14
+ /**
15
+ * Reports SERVING for the services registered on this server and NOT_FOUND for
16
+ * anything else. The set is frozen at registration; nothing is computed per
17
+ * request.
18
+ *
19
+ * `watch` is absent on purpose: it is server-streaming, and the body-hash
20
+ * signature scheme these servers run behind has no story for streams. Omitting
21
+ * it from a partial `ServiceImpl` makes connect-es answer UNIMPLEMENTED.
22
+ */
23
+ export const createHealthServiceImpl = (services, version) => {
24
+ const registered = new Set(services);
25
+ const serving = create(HealthCheckResponseSchema, {
26
+ status: HealthCheckResponse_ServingStatus.SERVING,
27
+ });
28
+ return {
29
+ check(req, ctx) {
30
+ ctx.responseHeader.set(SDK_ECOSYSTEM_HEADER, SDK_ECOSYSTEM);
31
+ if (version) {
32
+ ctx.responseHeader.set(SDK_VERSION_HEADER, version);
33
+ }
34
+ const { service } = req;
35
+ if (service !== "" && !registered.has(service)) {
36
+ throw new ConnectError(`unknown service '${service}'`, Code.NotFound);
37
+ }
38
+ return serving;
39
+ },
40
+ };
41
+ };
@@ -0,0 +1,157 @@
1
+ import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
2
+ import type { Message } from "@bufbuild/protobuf";
3
+ /**
4
+ * Describes the file grpc/health/v1/health.proto.
5
+ */
6
+ export declare const file_grpc_health_v1_health: GenFile;
7
+ /**
8
+ * @generated from message grpc.health.v1.HealthCheckRequest
9
+ */
10
+ export type HealthCheckRequest = Message<"grpc.health.v1.HealthCheckRequest"> & {
11
+ /**
12
+ * @generated from field: string service = 1;
13
+ */
14
+ service: string;
15
+ };
16
+ /**
17
+ * Describes the message grpc.health.v1.HealthCheckRequest.
18
+ * Use `create(HealthCheckRequestSchema)` to create a new message.
19
+ */
20
+ export declare const HealthCheckRequestSchema: GenMessage<HealthCheckRequest>;
21
+ /**
22
+ * @generated from message grpc.health.v1.HealthCheckResponse
23
+ */
24
+ export type HealthCheckResponse = Message<"grpc.health.v1.HealthCheckResponse"> & {
25
+ /**
26
+ * @generated from field: grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;
27
+ */
28
+ status: HealthCheckResponse_ServingStatus;
29
+ };
30
+ /**
31
+ * Describes the message grpc.health.v1.HealthCheckResponse.
32
+ * Use `create(HealthCheckResponseSchema)` to create a new message.
33
+ */
34
+ export declare const HealthCheckResponseSchema: GenMessage<HealthCheckResponse>;
35
+ /**
36
+ * @generated from enum grpc.health.v1.HealthCheckResponse.ServingStatus
37
+ */
38
+ export declare enum HealthCheckResponse_ServingStatus {
39
+ /**
40
+ * @generated from enum value: UNKNOWN = 0;
41
+ */
42
+ UNKNOWN = 0,
43
+ /**
44
+ * @generated from enum value: SERVING = 1;
45
+ */
46
+ SERVING = 1,
47
+ /**
48
+ * @generated from enum value: NOT_SERVING = 2;
49
+ */
50
+ NOT_SERVING = 2,
51
+ /**
52
+ * Used only by the Watch method.
53
+ *
54
+ * @generated from enum value: SERVICE_UNKNOWN = 3;
55
+ */
56
+ SERVICE_UNKNOWN = 3
57
+ }
58
+ /**
59
+ * Describes the enum grpc.health.v1.HealthCheckResponse.ServingStatus.
60
+ */
61
+ export declare const HealthCheckResponse_ServingStatusSchema: GenEnum<HealthCheckResponse_ServingStatus>;
62
+ /**
63
+ * @generated from message grpc.health.v1.HealthListRequest
64
+ */
65
+ export type HealthListRequest = Message<"grpc.health.v1.HealthListRequest"> & {};
66
+ /**
67
+ * Describes the message grpc.health.v1.HealthListRequest.
68
+ * Use `create(HealthListRequestSchema)` to create a new message.
69
+ */
70
+ export declare const HealthListRequestSchema: GenMessage<HealthListRequest>;
71
+ /**
72
+ * @generated from message grpc.health.v1.HealthListResponse
73
+ */
74
+ export type HealthListResponse = Message<"grpc.health.v1.HealthListResponse"> & {
75
+ /**
76
+ * statuses contains all the services and their respective status.
77
+ *
78
+ * @generated from field: map<string, grpc.health.v1.HealthCheckResponse> statuses = 1;
79
+ */
80
+ statuses: {
81
+ [key: string]: HealthCheckResponse;
82
+ };
83
+ };
84
+ /**
85
+ * Describes the message grpc.health.v1.HealthListResponse.
86
+ * Use `create(HealthListResponseSchema)` to create a new message.
87
+ */
88
+ export declare const HealthListResponseSchema: GenMessage<HealthListResponse>;
89
+ /**
90
+ * Health is gRPC's mechanism for checking whether a server is able to handle
91
+ * RPCs. Its semantics are documented in
92
+ * https://github.com/grpc/grpc/blob/master/doc/health-checking.md.
93
+ *
94
+ * @generated from service grpc.health.v1.Health
95
+ */
96
+ export declare const Health: GenService<{
97
+ /**
98
+ * Check gets the health of the specified service. If the requested service
99
+ * is unknown, the call will fail with status NOT_FOUND. If the caller does
100
+ * not specify a service name, the server should respond with its overall
101
+ * health status.
102
+ *
103
+ * Clients should set a deadline when calling Check, and can declare the
104
+ * server unhealthy if they do not receive a timely response.
105
+ *
106
+ * @generated from rpc grpc.health.v1.Health.Check
107
+ */
108
+ check: {
109
+ methodKind: "unary";
110
+ input: typeof HealthCheckRequestSchema;
111
+ output: typeof HealthCheckResponseSchema;
112
+ };
113
+ /**
114
+ * List provides a non-atomic snapshot of the health of all the available
115
+ * services.
116
+ *
117
+ * The server may respond with a RESOURCE_EXHAUSTED error if too many services
118
+ * exist.
119
+ *
120
+ * Clients should set a deadline when calling List, and can declare the server
121
+ * unhealthy if they do not receive a timely response.
122
+ *
123
+ * Clients should keep in mind that the list of health services exposed by an
124
+ * application can change over the lifetime of the process.
125
+ *
126
+ * @generated from rpc grpc.health.v1.Health.List
127
+ */
128
+ list: {
129
+ methodKind: "unary";
130
+ input: typeof HealthListRequestSchema;
131
+ output: typeof HealthListResponseSchema;
132
+ };
133
+ /**
134
+ * Performs a watch for the serving status of the requested service.
135
+ * The server will immediately send back a message indicating the current
136
+ * serving status. It will then subsequently send a new message whenever
137
+ * the service's serving status changes.
138
+ *
139
+ * If the requested service is unknown when the call is received, the
140
+ * server will send a message setting the serving status to
141
+ * SERVICE_UNKNOWN but will *not* terminate the call. If at some
142
+ * future point, the serving status of the service becomes known, the
143
+ * server will send a new message with the service's serving status.
144
+ *
145
+ * If the call terminates with status UNIMPLEMENTED, then clients
146
+ * should assume this method is not supported and should not retry the
147
+ * call. If the call terminates with any other status (including OK),
148
+ * clients should retry the call with appropriate exponential backoff.
149
+ *
150
+ * @generated from rpc grpc.health.v1.Health.Watch
151
+ */
152
+ watch: {
153
+ methodKind: "server_streaming";
154
+ input: typeof HealthCheckRequestSchema;
155
+ output: typeof HealthCheckResponseSchema;
156
+ };
157
+ }>;