@calimero-network/mero-js 26.10.0 → 27.0.0

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 (220) hide show
  1. package/README.md +73 -288
  2. package/dist/cloud-v2/attestation.d.ts +91 -0
  3. package/dist/cloud-v2/attestation.js +135 -0
  4. package/dist/cloud-v2/capabilities.d.ts +30 -0
  5. package/dist/cloud-v2/capabilities.js +31 -0
  6. package/dist/cloud-v2/client.d.ts +426 -0
  7. package/dist/cloud-v2/client.js +646 -0
  8. package/dist/cloud-v2/errors.d.ts +98 -0
  9. package/dist/cloud-v2/errors.js +165 -0
  10. package/dist/cloud-v2/index.d.ts +10 -0
  11. package/dist/cloud-v2/index.js +5 -0
  12. package/dist/cloud-v2/passkey.d.ts +27 -0
  13. package/dist/cloud-v2/passkey.js +73 -0
  14. package/dist/crypto/internal.d.ts +3 -37
  15. package/dist/crypto/internal.js +3 -72
  16. package/dist/index.browser.mjs +4 -2
  17. package/dist/index.browser.mjs.map +4 -4
  18. package/dist/index.cjs +1750 -12767
  19. package/dist/index.cjs.map +4 -4
  20. package/dist/index.d.ts +3 -56
  21. package/dist/index.js +14 -75
  22. package/dist/index.mjs +1750 -12767
  23. package/dist/index.mjs.map +4 -4
  24. package/dist/sealed/index.d.ts +4 -4
  25. package/dist/sealed/index.js +2 -2
  26. package/dist/sealed/release.d.ts +21 -76
  27. package/dist/sealed/release.js +24 -108
  28. package/dist/sealed/sealed.d.ts +8 -56
  29. package/dist/sealed/sealed.js +11 -89
  30. package/dist/sealed/verify.d.ts +7 -13
  31. package/dist/sealed/verify.js +3 -7
  32. package/package.json +8 -13
  33. package/dist/account/account-admin.d.ts +0 -80
  34. package/dist/account/account-admin.js +0 -346
  35. package/dist/account/account.d.ts +0 -196
  36. package/dist/account/account.js +0 -269
  37. package/dist/account/application-id.d.ts +0 -50
  38. package/dist/account/application-id.js +0 -83
  39. package/dist/account/bootstrap-from-invitation.d.ts +0 -110
  40. package/dist/account/bootstrap-from-invitation.js +0 -80
  41. package/dist/account/create-context.d.ts +0 -103
  42. package/dist/account/create-context.js +0 -267
  43. package/dist/account/govern.d.ts +0 -24
  44. package/dist/account/govern.js +0 -50
  45. package/dist/account/index.d.ts +0 -26
  46. package/dist/account/index.js +0 -15
  47. package/dist/account/join-as-account.d.ts +0 -23
  48. package/dist/account/join-as-account.js +0 -34
  49. package/dist/account/join-with-node.d.ts +0 -115
  50. package/dist/account/join-with-node.js +0 -161
  51. package/dist/account/node-admin.d.ts +0 -20
  52. package/dist/account/node-admin.js +0 -33
  53. package/dist/account/relay-from-invitation.d.ts +0 -137
  54. package/dist/account/relay-from-invitation.js +0 -180
  55. package/dist/account/relay-login-error.d.ts +0 -32
  56. package/dist/account/relay-login-error.js +0 -34
  57. package/dist/account/session.d.ts +0 -266
  58. package/dist/account/session.js +0 -915
  59. package/dist/account/wordlist.d.ts +0 -16
  60. package/dist/account/wordlist.js +0 -273
  61. package/dist/admin-api/admin-client.d.ts +0 -686
  62. package/dist/admin-api/admin-client.js +0 -1225
  63. package/dist/admin-api/admin-factory.d.ts +0 -14
  64. package/dist/admin-api/admin-factory.js +0 -22
  65. package/dist/admin-api/admin-types.d.ts +0 -2041
  66. package/dist/admin-api/admin-types.js +0 -12
  67. package/dist/admin-api/index.d.ts +0 -4
  68. package/dist/admin-api/index.js +0 -5
  69. package/dist/admin-api/redeem-invitation.d.ts +0 -101
  70. package/dist/admin-api/redeem-invitation.js +0 -200
  71. package/dist/auth/index.d.ts +0 -34
  72. package/dist/auth/index.js +0 -67
  73. package/dist/auth-api/auth-client.d.ts +0 -31
  74. package/dist/auth-api/auth-client.js +0 -117
  75. package/dist/auth-api/auth-factory.d.ts +0 -7
  76. package/dist/auth-api/auth-factory.js +0 -41
  77. package/dist/auth-api/auth-types.d.ts +0 -130
  78. package/dist/auth-api/auth-types.js +0 -2
  79. package/dist/auth-api/index.d.ts +0 -3
  80. package/dist/auth-api/index.js +0 -4
  81. package/dist/capabilities.d.ts +0 -84
  82. package/dist/capabilities.js +0 -117
  83. package/dist/cloud/account-ownership.d.ts +0 -63
  84. package/dist/cloud/account-ownership.js +0 -87
  85. package/dist/cloud/cloud-client.d.ts +0 -949
  86. package/dist/cloud/cloud-client.js +0 -1068
  87. package/dist/cloud/connect.d.ts +0 -213
  88. package/dist/cloud/connect.js +0 -260
  89. package/dist/cloud/enrol-redirect.d.ts +0 -181
  90. package/dist/cloud/enrol-redirect.js +0 -244
  91. package/dist/cloud/index.d.ts +0 -12
  92. package/dist/cloud/index.js +0 -10
  93. package/dist/cloud/link-redirect.d.ts +0 -95
  94. package/dist/cloud/link-redirect.js +0 -121
  95. package/dist/cloud/link-state.d.ts +0 -36
  96. package/dist/cloud/link-state.js +0 -104
  97. package/dist/cloud/routing-proof.d.ts +0 -113
  98. package/dist/cloud/routing-proof.js +0 -96
  99. package/dist/device-cert/device-cert.d.ts +0 -189
  100. package/dist/device-cert/device-cert.js +0 -239
  101. package/dist/device-cert/device-scope.d.ts +0 -58
  102. package/dist/device-cert/device-scope.js +0 -76
  103. package/dist/device-cert/index.d.ts +0 -4
  104. package/dist/device-cert/index.js +0 -3
  105. package/dist/ephemeral/codec.d.ts +0 -4
  106. package/dist/ephemeral/codec.js +0 -12
  107. package/dist/ephemeral/index.d.ts +0 -57
  108. package/dist/ephemeral/index.js +0 -101
  109. package/dist/ephemeral/types.d.ts +0 -27
  110. package/dist/ephemeral/types.js +0 -1
  111. package/dist/errors/classify.d.ts +0 -65
  112. package/dist/errors/classify.js +0 -117
  113. package/dist/errors/index.d.ts +0 -2
  114. package/dist/errors/index.js +0 -1
  115. package/dist/events/group.d.ts +0 -95
  116. package/dist/events/group.js +0 -10
  117. package/dist/events/index.d.ts +0 -5
  118. package/dist/events/index.js +0 -2
  119. package/dist/events/sse.d.ts +0 -127
  120. package/dist/events/sse.js +0 -469
  121. package/dist/events/ws.d.ts +0 -57
  122. package/dist/events/ws.js +0 -212
  123. package/dist/http-client/api-response.d.ts +0 -15
  124. package/dist/http-client/api-response.js +0 -1
  125. package/dist/http-client/http-factory.d.ts +0 -71
  126. package/dist/http-client/http-factory.js +0 -68
  127. package/dist/http-client/http-types.d.ts +0 -87
  128. package/dist/http-client/http-types.js +0 -1
  129. package/dist/http-client/index.d.ts +0 -7
  130. package/dist/http-client/index.js +0 -11
  131. package/dist/http-client/path-segment.d.ts +0 -1
  132. package/dist/http-client/path-segment.js +0 -8
  133. package/dist/http-client/retry.d.ts +0 -5
  134. package/dist/http-client/retry.js +0 -82
  135. package/dist/http-client/signal-utils.d.ts +0 -2
  136. package/dist/http-client/signal-utils.js +0 -41
  137. package/dist/http-client/web-client.d.ts +0 -80
  138. package/dist/http-client/web-client.js +0 -557
  139. package/dist/invitation/index.d.ts +0 -2
  140. package/dist/invitation/index.js +0 -2
  141. package/dist/invitation/invitation.d.ts +0 -106
  142. package/dist/invitation/invitation.js +0 -104
  143. package/dist/login/index.d.ts +0 -4
  144. package/dist/login/index.js +0 -3
  145. package/dist/login/login.d.ts +0 -111
  146. package/dist/login/login.js +0 -110
  147. package/dist/login/session.d.ts +0 -124
  148. package/dist/login/session.js +0 -141
  149. package/dist/mero-js.d.ts +0 -231
  150. package/dist/mero-js.js +0 -469
  151. package/dist/namespace-op/index.d.ts +0 -2
  152. package/dist/namespace-op/index.js +0 -3
  153. package/dist/namespace-op/namespace-op.d.ts +0 -150
  154. package/dist/namespace-op/namespace-op.js +0 -191
  155. package/dist/nodeDiscovery.d.ts +0 -47
  156. package/dist/nodeDiscovery.js +0 -101
  157. package/dist/owner-op/index.d.ts +0 -2
  158. package/dist/owner-op/index.js +0 -3
  159. package/dist/owner-op/owner-op.d.ts +0 -119
  160. package/dist/owner-op/owner-op.js +0 -214
  161. package/dist/presence/relay-presence.d.ts +0 -56
  162. package/dist/presence/relay-presence.js +0 -107
  163. package/dist/presence/statement.d.ts +0 -12
  164. package/dist/presence/statement.js +0 -17
  165. package/dist/relay/index.d.ts +0 -4
  166. package/dist/relay/index.js +0 -2
  167. package/dist/relay/nonce-source.d.ts +0 -72
  168. package/dist/relay/nonce-source.js +0 -111
  169. package/dist/relay/relay-client.d.ts +0 -472
  170. package/dist/relay/relay-client.js +0 -518
  171. package/dist/relay-attestation/attest-node-key.d.ts +0 -66
  172. package/dist/relay-attestation/attest-node-key.js +0 -110
  173. package/dist/relay-attestation/index.d.ts +0 -1
  174. package/dist/relay-attestation/index.js +0 -1
  175. package/dist/request/caller-proof.d.ts +0 -43
  176. package/dist/request/caller-proof.js +0 -36
  177. package/dist/request/index.d.ts +0 -3
  178. package/dist/request/index.js +0 -3
  179. package/dist/request/proof-signer.d.ts +0 -49
  180. package/dist/request/proof-signer.js +0 -59
  181. package/dist/request/request-sig.d.ts +0 -99
  182. package/dist/request/request-sig.js +0 -95
  183. package/dist/request-proof/authorizer.d.ts +0 -41
  184. package/dist/request-proof/authorizer.js +0 -60
  185. package/dist/request-proof/index.d.ts +0 -4
  186. package/dist/request-proof/index.js +0 -2
  187. package/dist/request-proof/request-proof.d.ts +0 -53
  188. package/dist/request-proof/request-proof.js +0 -95
  189. package/dist/rpc/index.d.ts +0 -64
  190. package/dist/rpc/index.js +0 -78
  191. package/dist/signer/index.d.ts +0 -2
  192. package/dist/signer/index.js +0 -3
  193. package/dist/signer/signer.d.ts +0 -92
  194. package/dist/signer/signer.js +0 -147
  195. package/dist/token-store/index.d.ts +0 -27
  196. package/dist/token-store/index.js +0 -62
  197. package/dist/transport/index.d.ts +0 -6
  198. package/dist/transport/index.js +0 -3
  199. package/dist/transport/mero-client.d.ts +0 -294
  200. package/dist/transport/mero-client.js +0 -386
  201. package/dist/transport/relay-observer.d.ts +0 -152
  202. package/dist/transport/relay-observer.js +0 -173
  203. package/dist/transport/relay-transport.d.ts +0 -73
  204. package/dist/transport/relay-transport.js +0 -94
  205. package/dist/transport/types.d.ts +0 -100
  206. package/dist/transport/types.js +0 -32
  207. package/dist/types/api-response.d.ts +0 -15
  208. package/dist/types/api-response.js +0 -1
  209. package/dist/types/index.d.ts +0 -1
  210. package/dist/types/index.js +0 -1
  211. package/dist/warrant/creation-warrant.d.ts +0 -163
  212. package/dist/warrant/creation-warrant.js +0 -194
  213. package/dist/warrant/governance-op.d.ts +0 -202
  214. package/dist/warrant/governance-op.js +0 -355
  215. package/dist/warrant/governance-warrant.d.ts +0 -124
  216. package/dist/warrant/governance-warrant.js +0 -164
  217. package/dist/warrant/index.d.ts +0 -8
  218. package/dist/warrant/index.js +0 -5
  219. package/dist/warrant/warrant.d.ts +0 -188
  220. package/dist/warrant/warrant.js +0 -202
@@ -1,110 +0,0 @@
1
- import { hex } from '../crypto/internal.js';
2
- /** Core's `ATTEST_KEY_BINDING_DOMAIN`. */
3
- const KEY_BINDING_DOMAIN = new TextEncoder().encode('calimero.tee-attest.key-binding.v1');
4
- /** Core's `MOCK_QUOTE_HEADER`: a mock quote is this, then the 64-byte report data. */
5
- const MOCK_QUOTE_HEADER = new TextEncoder().encode('MOCK_TDX_QUOTE_V1');
6
- /** A TDX v4 quote: 48-byte header, then the TD report body; report data at body offset 520. */
7
- const TDX_REPORT_DATA_OFFSET = 48 + 520;
8
- function concat(...parts) {
9
- const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
10
- let at = 0;
11
- for (const p of parts) {
12
- out.set(p, at);
13
- at += p.length;
14
- }
15
- return out;
16
- }
17
- function equal(a, b) {
18
- if (a.length !== b.length)
19
- return false;
20
- let diff = 0;
21
- for (let i = 0; i < a.length; i += 1)
22
- diff |= a[i] ^ b[i];
23
- return diff === 0;
24
- }
25
- function fromBase64(b64) {
26
- const bin = atob(b64);
27
- const out = new Uint8Array(bin.length);
28
- for (let i = 0; i < bin.length; i += 1)
29
- out[i] = bin.charCodeAt(i);
30
- return out;
31
- }
32
- function fromHex32(value, label) {
33
- if (typeof value !== 'string' || !/^[0-9a-fA-F]{64}$/.test(value)) {
34
- throw new Error(`${label} must be 64 hex characters`);
35
- }
36
- const out = new Uint8Array(32);
37
- for (let i = 0; i < 32; i += 1)
38
- out[i] = Number.parseInt(value.slice(i * 2, i * 2 + 2), 16);
39
- return out;
40
- }
41
- /** Core's `attest_key_binding(None, key)`. */
42
- export async function attestKeyBinding(nodeKey) {
43
- const digest = await crypto.subtle.digest('SHA-256', concat(KEY_BINDING_DOMAIN, new Uint8Array(32), nodeKey));
44
- return new Uint8Array(digest);
45
- }
46
- /** The 64 report-data bytes of a quote, read from the quote itself. */
47
- export function reportDataOf(quote) {
48
- const mock = quote.length >= MOCK_QUOTE_HEADER.length &&
49
- equal(quote.subarray(0, MOCK_QUOTE_HEADER.length), MOCK_QUOTE_HEADER);
50
- const at = mock ? MOCK_QUOTE_HEADER.length : TDX_REPORT_DATA_OFFSET;
51
- if (quote.length < at + 64) {
52
- throw new Error(`the quote is too short to carry report data (${quote.length} bytes)`);
53
- }
54
- return { reportData: quote.slice(at, at + 64), mock };
55
- }
56
- /**
57
- * Ask a relay for a quote binding its node key to a fresh nonce, check the
58
- * binding, and return the key.
59
- */
60
- export async function attestRelayNodeKey(opts) {
61
- const nonce = crypto.getRandomValues(new Uint8Array(32));
62
- const url = `${opts.relayUrl.replace(/\/+$/, '')}/admin-api/tee/attest`;
63
- const body = JSON.stringify({
64
- nonce: hex(nonce),
65
- bindNodeKey: true,
66
- ...(opts.verify?.includeCollateral ? { includeCollateral: true } : {}),
67
- });
68
- const init = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body };
69
- const response = opts.fetch ? await opts.fetch(url, init) : await globalThis.fetch(url, init);
70
- if (!response.ok) {
71
- const text = await response.text().catch(() => '');
72
- throw new Error(`the relay would not attest (HTTP ${response.status})${text ? `: ${text}` : ''}`);
73
- }
74
- const parsed = (await response.json());
75
- const data = parsed.data ?? parsed;
76
- if (typeof data.quoteB64 !== 'string')
77
- throw new Error('the relay returned no quote');
78
- const nodeKey = fromHex32(data.boundPublicKey, 'boundPublicKey');
79
- const quote = fromBase64(data.quoteB64);
80
- const { reportData, mock } = reportDataOf(quote);
81
- if (mock) {
82
- if (!opts.allowMock) {
83
- throw new Error('the relay answered with a MOCK quote, which proves nothing about the hardware');
84
- }
85
- }
86
- else if (opts.verify) {
87
- const accepted = await opts.verify({
88
- quoteB64: data.quoteB64,
89
- nonce: hex(nonce),
90
- reportDataSuffix: hex(await attestKeyBinding(nodeKey)),
91
- ...(data.collateral ? { collateral: data.collateral } : {}),
92
- });
93
- if (!accepted) {
94
- throw new Error("the relay's quote did not verify: not a trusted image, or not bound to this request");
95
- }
96
- }
97
- else if (opts.verifyQuote) {
98
- await opts.verifyQuote(quote);
99
- }
100
- else {
101
- throw new Error('a real TDX quote needs a verify (or verifyQuote) to check its signature and measurements');
102
- }
103
- if (!equal(reportData.subarray(0, 32), nonce)) {
104
- throw new Error('the quote does not carry our nonce: it was not made for this request');
105
- }
106
- if (!equal(reportData.subarray(32, 64), await attestKeyBinding(nodeKey))) {
107
- throw new Error('the quote does not bind the key the relay named: refusing it');
108
- }
109
- return { nodeKey: hex(nodeKey), mock };
110
- }
@@ -1 +0,0 @@
1
- export { attestRelayNodeKey, attestKeyBinding, reportDataOf, type AttestedNodeKey, type AttestRelayNodeKeyOptions, } from './attest-node-key.js';
@@ -1 +0,0 @@
1
- export { attestRelayNodeKey, attestKeyBinding, reportDataOf, } from './attest-node-key.js';
@@ -1,43 +0,0 @@
1
- /**
2
- * Assemble the credential a node reads from `X-Calimero-Proof`.
3
- *
4
- * A request-carried proof is three links, and the node needs all of them in one
5
- * header: the account root certified the device, the device (optionally)
6
- * certified a short-lived session key, and that key signed THIS request. Each
7
- * link is minted by its own function here — {@link signDeviceCert},
8
- * {@link signLoginStatement}, {@link signRequest} — and each already returns
9
- * the borsh encoding of the Rust type the node deserializes. Rust's
10
- * `CallerProof` is a plain struct of those three fields, and borsh writes a
11
- * struct as its fields back to back, so assembling the header is concatenation
12
- * rather than a fourth encoder.
13
- *
14
- * That is worth stating because it is load-bearing and it is not obvious: if it
15
- * were re-encoded here there would be a second spelling of a signed structure,
16
- * able to disagree with the one that was signed. `caller-proof.test.ts` pins the
17
- * concatenation against bytes produced by core itself.
18
- */
19
- /** The three links, each already hex-encoded by the function that signed it. */
20
- export interface CallerProofInput {
21
- /**
22
- * `signDeviceCert(...)` — the account root's certificate over the device key,
23
- * with the genesis that names the account.
24
- */
25
- credential: string;
26
- /**
27
- * `signLoginStatement(...)`, when signing with a session key.
28
- *
29
- * Omit it on the two-link chain, where the device key signs the request
30
- * itself. Omitting is not the same as passing an empty string: borsh writes
31
- * `Option` as a tag byte, so the two produce different bytes and only one of
32
- * them parses.
33
- */
34
- session?: string;
35
- /** `signRequest(...)` — the signature over this exact method, path and body. */
36
- request: string;
37
- }
38
- /**
39
- * Concatenate the three links into the header value.
40
- *
41
- * Returns hex, ready to present as `X-Calimero-Proof`.
42
- */
43
- export declare function callerProof(input: CallerProofInput): string;
@@ -1,36 +0,0 @@
1
- /**
2
- * Assemble the credential a node reads from `X-Calimero-Proof`.
3
- *
4
- * A request-carried proof is three links, and the node needs all of them in one
5
- * header: the account root certified the device, the device (optionally)
6
- * certified a short-lived session key, and that key signed THIS request. Each
7
- * link is minted by its own function here — {@link signDeviceCert},
8
- * {@link signLoginStatement}, {@link signRequest} — and each already returns
9
- * the borsh encoding of the Rust type the node deserializes. Rust's
10
- * `CallerProof` is a plain struct of those three fields, and borsh writes a
11
- * struct as its fields back to back, so assembling the header is concatenation
12
- * rather than a fourth encoder.
13
- *
14
- * That is worth stating because it is load-bearing and it is not obvious: if it
15
- * were re-encoded here there would be a second spelling of a signed structure,
16
- * able to disagree with the one that was signed. `caller-proof.test.ts` pins the
17
- * concatenation against bytes produced by core itself.
18
- */
19
- import { fromHexUnsized, hex, concat } from "../crypto/internal.js";
20
- /**
21
- * Concatenate the three links into the header value.
22
- *
23
- * Returns hex, ready to present as `X-Calimero-Proof`.
24
- */
25
- export function callerProof(input) {
26
- const credential = fromHexUnsized(input.credential, "credential");
27
- const request = fromHexUnsized(input.request, "request signature");
28
- // Borsh `Option<T>`: a single tag byte, then the value when present. A
29
- // two-link chain is `None`, not an empty `Some` — the node reads the tag
30
- // before anything else, so getting it backwards fails to parse rather than
31
- // failing to verify, which at least is loud.
32
- const session = input.session === undefined
33
- ? new Uint8Array([0])
34
- : concat(new Uint8Array([1]), fromHexUnsized(input.session, "session statement"));
35
- return hex(concat(credential, session, request));
36
- }
@@ -1,3 +0,0 @@
1
- export { requestBodyHash, signRequest, type RequestSigInput, } from "./request-sig.js";
2
- export { callerProof, type CallerProofInput } from "./caller-proof.js";
3
- export { createProofSigner, type ProofRequest, type ProofSignerOptions, } from "./proof-signer.js";
@@ -1,3 +0,0 @@
1
- export { requestBodyHash, signRequest, } from "./request-sig.js";
2
- export { callerProof } from "./caller-proof.js";
3
- export { createProofSigner, } from "./proof-signer.js";
@@ -1,49 +0,0 @@
1
- /**
2
- * Turn a device key into something the HTTP client can call per request.
3
- *
4
- * A session is a bearer token: mint once, present many times. A request-carried
5
- * proof is the opposite — it commits to one method, one path and one body, so
6
- * it has to be produced at the moment of the call, by something holding the
7
- * signing key. This is that something, shaped like `getAuthToken` so the key
8
- * stays with the caller and never reaches the transport.
9
- */
10
- /** What the transport knows about the call it is about to make. */
11
- export interface ProofRequest {
12
- /** `GET`, `POST`, … exactly as it will be sent. */
13
- method: string;
14
- /** The path and query the node will see, as `signedPath` reads them. */
15
- path: string;
16
- /** The exact bytes that will be sent, if any. */
17
- body?: unknown;
18
- }
19
- export interface ProofSignerOptions {
20
- /** `signDeviceCert(...)`, hex. */
21
- credential: string;
22
- /**
23
- * `signLoginStatement(...)`, hex — the three-link chain.
24
- *
25
- * Omit for the two-link chain, where `signerSecret` is the device key itself.
26
- */
27
- session?: string;
28
- /**
29
- * The ed25519 secret that signs each request, hex: the session key when
30
- * `session` is set, otherwise the device key.
31
- */
32
- signerSecret: string;
33
- /**
34
- * How long each signature stays valid. Seconds, default 120.
35
- *
36
- * This is what bounds replay and nothing else does, so it is deliberately
37
- * short and deliberately not configurable to "never". A clock skew allowance
38
- * lives on the node; this only has to outlive the flight time.
39
- */
40
- ttlSeconds?: number;
41
- /** Unix seconds. Injectable so a test does not depend on the wall clock. */
42
- now?: () => number;
43
- }
44
- /**
45
- * Build the `X-Calimero-Proof` value for one request.
46
- *
47
- * Pass the result to the HTTP client's `getProof`.
48
- */
49
- export declare function createProofSigner(options: ProofSignerOptions): (request: ProofRequest) => Promise<string>;
@@ -1,59 +0,0 @@
1
- /**
2
- * Turn a device key into something the HTTP client can call per request.
3
- *
4
- * A session is a bearer token: mint once, present many times. A request-carried
5
- * proof is the opposite — it commits to one method, one path and one body, so
6
- * it has to be produced at the moment of the call, by something holding the
7
- * signing key. This is that something, shaped like `getAuthToken` so the key
8
- * stays with the caller and never reaches the transport.
9
- */
10
- import { callerProof } from "./caller-proof.js";
11
- import { signRequest } from "./request-sig.js";
12
- /**
13
- * The bytes a body will actually be sent as.
14
- *
15
- * Refuses what it cannot hash without consuming or re-encoding. A stream can be
16
- * read once, and reading it here would send an empty body; `FormData` and
17
- * `URLSearchParams` are serialized by `fetch` in ways this cannot reproduce
18
- * byte for byte. Signing a guess would produce a signature over bytes that
19
- * never travelled, and the node would reject it with a signature error that
20
- * says nothing about the real cause — so this throws, naming the type.
21
- */
22
- function bodyBytes(body) {
23
- if (body === undefined || body === null)
24
- return new Uint8Array(0);
25
- if (typeof body === "string")
26
- return body;
27
- if (body instanceof Uint8Array)
28
- return body;
29
- if (body instanceof ArrayBuffer)
30
- return new Uint8Array(body);
31
- if (ArrayBuffer.isView(body)) {
32
- return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
33
- }
34
- throw new Error(`a request-carried proof cannot commit to a ${body?.constructor?.name ?? typeof body} body: pass a string or bytes, or use a session for this call`);
35
- }
36
- /**
37
- * Build the `X-Calimero-Proof` value for one request.
38
- *
39
- * Pass the result to the HTTP client's `getProof`.
40
- */
41
- export function createProofSigner(options) {
42
- const ttl = options.ttlSeconds ?? 120;
43
- const clock = options.now ?? (() => Math.floor(Date.now() / 1000));
44
- return async (request) => {
45
- const issuedAt = clock();
46
- return callerProof({
47
- credential: options.credential,
48
- session: options.session,
49
- request: await signRequest({
50
- method: request.method,
51
- path: request.path,
52
- body: bodyBytes(request.body),
53
- issuedAt,
54
- expiresAt: issuedAt + ttl,
55
- signerSecret: options.signerSecret,
56
- }),
57
- });
58
- };
59
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * Sign one HTTP request, so a caller's identity travels with it.
3
- *
4
- * This is the bottom link of the chain a delegated client presents. A session
5
- * has the node mint and remember something; this has the caller prove itself on
6
- * every call, so the node holds no session state and the same path works
7
- * whether or not the node runs a login provider at all.
8
- *
9
- * ```text
10
- * account root ──signs──▶ DeviceCert cold, once, offline
11
- * device key ──signs──▶ LoginStatement once per session, hours
12
- * session key ──signs──▶ RequestSig per call ◀── this module
13
- * ```
14
- *
15
- * **The middle link is optional.** A CLI or a server-side job signs with its
16
- * device key directly and presents two links; a browser keeping the device key
17
- * behind a hardware or extension boundary mints a session key and presents
18
- * three. The node's verifier handles both, so which you produce here is a
19
- * question about where your key lives, not about what the node accepts.
20
- *
21
- * **What it binds.** Method, path and query, and a hash of the body. A
22
- * signature for `GET /a?x=1` is not a signature for `POST /a?x=1`, for
23
- * `GET /a?x=2`, for `GET /b?x=1`, or for the same call with a different body.
24
- *
25
- * **The path includes the query string**, exactly as it goes on the request
26
- * line: no re-encoding, no reordering. A proxy between you and the node must
27
- * forward both unchanged, or the node refuses the proof.
28
- *
29
- * **The method is not normalized.** `HEAD` and `GET` are the same *permission*
30
- * and the node's permission layer folds them, but they are different requests.
31
- * Sign the method you are about to send.
32
- *
33
- * **Replay inside the window performs the identical request** and is bounded
34
- * rather than prevented — keep `expiresAt` short. A warrant, which authorizes a
35
- * change with lasting effect, carries a nonce ledger for this; a request
36
- * signature does not.
37
- *
38
- * **The byte contract is pinned in core**, at
39
- * `crates/account/src/tests/request_wire_fixture.rs`, for the same reason the
40
- * warrant and login ones are: the domains are `pub(crate)` there and the field
41
- * order is implicit in a `derive`, so nothing on that side forces anyone to
42
- * notice this file depends on both. This module's test asserts the same vectors.
43
- */
44
- import { fromHex } from "../crypto/internal.js";
45
- export declare const SIGN_DOMAIN: Uint8Array;
46
- /**
47
- * Commit to a request body.
48
- *
49
- * Always call it, including for a body-less request: an empty body hashes to a
50
- * value like any other, and there is no "absent body" encoding for a verifier
51
- * to disagree with you about.
52
- */
53
- export declare function requestBodyHash(body?: Uint8Array | string): Promise<Uint8Array>;
54
- /** What a caller is about to send. */
55
- export interface RequestSigInput {
56
- /**
57
- * The HTTP method, exactly as it will appear on the wire — `GET`, `POST`,
58
- * `HEAD`. Not folded: see the module note.
59
- */
60
- method: string;
61
- /**
62
- * The path and query, exactly as on the request line:
63
- * `/admin-api/blobs?context_id=…`. Use `signedPath` to take it from a URL.
64
- */
65
- path: string;
66
- /**
67
- * The request body, or nothing. Pass exactly the bytes you will send: the
68
- * signature commits to their hash, so a body re-serialized between signing
69
- * and sending is a signature for a different request.
70
- */
71
- body?: Uint8Array | string;
72
- /** Unix seconds at signing. */
73
- issuedAt: number | bigint;
74
- /**
75
- * Unix seconds after which the node must refuse it.
76
- *
77
- * Keep it short — minutes, not hours. It is what bounds replay, and nothing
78
- * else does.
79
- */
80
- expiresAt: number | bigint;
81
- /**
82
- * The ed25519 signing secret, hex (32 bytes): the session key on the
83
- * three-link chain, the device key on the two-link one.
84
- *
85
- * Never sent anywhere. It signs locally and only the signature travels.
86
- */
87
- signerSecret: string;
88
- }
89
- /**
90
- * Sign one request and return it hex-encoded, ready to present.
91
- *
92
- * Returns the encoding rather than an object for the reason `signWarrant` and
93
- * `signLoginStatement` do: the signature covers exactly these bytes, and a
94
- * caller that rebuilt the fields from JSON would have a second spelling able to
95
- * disagree with what was signed.
96
- */
97
- export declare function signRequest(input: RequestSigInput): Promise<string>;
98
- /** Re-exported so a caller can check a hex signer key before signing with it. */
99
- export { fromHex };
@@ -1,95 +0,0 @@
1
- /**
2
- * Sign one HTTP request, so a caller's identity travels with it.
3
- *
4
- * This is the bottom link of the chain a delegated client presents. A session
5
- * has the node mint and remember something; this has the caller prove itself on
6
- * every call, so the node holds no session state and the same path works
7
- * whether or not the node runs a login provider at all.
8
- *
9
- * ```text
10
- * account root ──signs──▶ DeviceCert cold, once, offline
11
- * device key ──signs──▶ LoginStatement once per session, hours
12
- * session key ──signs──▶ RequestSig per call ◀── this module
13
- * ```
14
- *
15
- * **The middle link is optional.** A CLI or a server-side job signs with its
16
- * device key directly and presents two links; a browser keeping the device key
17
- * behind a hardware or extension boundary mints a session key and presents
18
- * three. The node's verifier handles both, so which you produce here is a
19
- * question about where your key lives, not about what the node accepts.
20
- *
21
- * **What it binds.** Method, path and query, and a hash of the body. A
22
- * signature for `GET /a?x=1` is not a signature for `POST /a?x=1`, for
23
- * `GET /a?x=2`, for `GET /b?x=1`, or for the same call with a different body.
24
- *
25
- * **The path includes the query string**, exactly as it goes on the request
26
- * line: no re-encoding, no reordering. A proxy between you and the node must
27
- * forward both unchanged, or the node refuses the proof.
28
- *
29
- * **The method is not normalized.** `HEAD` and `GET` are the same *permission*
30
- * and the node's permission layer folds them, but they are different requests.
31
- * Sign the method you are about to send.
32
- *
33
- * **Replay inside the window performs the identical request** and is bounded
34
- * rather than prevented — keep `expiresAt` short. A warrant, which authorizes a
35
- * change with lasting effect, carries a nonce ledger for this; a request
36
- * signature does not.
37
- *
38
- * **The byte contract is pinned in core**, at
39
- * `crates/account/src/tests/request_wire_fixture.rs`, for the same reason the
40
- * warrant and login ones are: the domains are `pub(crate)` there and the field
41
- * order is implicit in a `derive`, so nothing on that side forces anyone to
42
- * notice this file depends on both. This module's test asserts the same vectors.
43
- */
44
- import { concat, domainHash, fromHex, hex, importSigningKey, u32le, u64le, } from "../crypto/internal.js";
45
- export const SIGN_DOMAIN = new TextEncoder().encode("calimero.auth.request.v2");
46
- /**
47
- * Its own domain, distinct from the signing one.
48
- *
49
- * The two are different jobs on the same bytes: this produces the commitment,
50
- * the other signs over it. Sharing a domain would make the commitment a
51
- * truncated disclosure of bytes something signs.
52
- */
53
- const BODY_DOMAIN = new TextEncoder().encode("calimero.auth.request.body.v1");
54
- /** A borsh `String`: its own `u32` little-endian length, then the UTF-8. */
55
- function borshString(value) {
56
- const bytes = new TextEncoder().encode(value);
57
- return concat(u32le(bytes.length), bytes);
58
- }
59
- /**
60
- * Commit to a request body.
61
- *
62
- * Always call it, including for a body-less request: an empty body hashes to a
63
- * value like any other, and there is no "absent body" encoding for a verifier
64
- * to disagree with you about.
65
- */
66
- export async function requestBodyHash(body = new Uint8Array(0)) {
67
- const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body;
68
- return domainHash(BODY_DOMAIN, [bytes]);
69
- }
70
- /**
71
- * Sign one request and return it hex-encoded, ready to present.
72
- *
73
- * Returns the encoding rather than an object for the reason `signWarrant` and
74
- * `signLoginStatement` do: the signature covers exactly these bytes, and a
75
- * caller that rebuilt the fields from JSON would have a second spelling able to
76
- * disagree with what was signed.
77
- */
78
- export async function signRequest(input) {
79
- const key = await importSigningKey(input.signerSecret);
80
- const bodyHash = await requestBodyHash(input.body);
81
- const issuedAt = u64le(input.issuedAt);
82
- const expiresAt = u64le(input.expiresAt);
83
- const encoder = new TextEncoder();
84
- const preimage = await domainHash(SIGN_DOMAIN, [
85
- encoder.encode(input.method),
86
- encoder.encode(input.path),
87
- bodyHash,
88
- issuedAt,
89
- expiresAt,
90
- ]);
91
- const signature = new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, key, preimage));
92
- return hex(concat(borshString(input.method), borshString(input.path), bodyHash, issuedAt, expiresAt, signature));
93
- }
94
- /** Re-exported so a caller can check a hex signer key before signing with it. */
95
- export { fromHex };
@@ -1,41 +0,0 @@
1
- import type { Signer } from '../signer/index.js';
2
- /** What a keyholder holds: a certificate, and the key it certifies. */
3
- export interface ProofCredential {
4
- /** The `AccountProof<DeviceCert>` from `signDeviceCert`, hex. */
5
- credential: string;
6
- /** The certified device's ed25519 secret, hex. Or pass `signer`. */
7
- deviceSecret?: string;
8
- /** A signer, for a key that cannot be exported to hex. */
9
- signer?: Signer;
10
- /** Seconds each signature stays valid. Defaults to `signRequestProof`'s. */
11
- validForSeconds?: number;
12
- }
13
- /** One request, as the proof has to see it. */
14
- export interface RequestToAuthorize {
15
- method: string;
16
- /** Path and query, as `signedPath` reads them from the URL being fetched. */
17
- path: string;
18
- /** The body exactly as it will be sent, if there is one. */
19
- body?: string | Uint8Array;
20
- }
21
- /**
22
- * A per-request authorizer. Returns the headers to merge into the request.
23
- *
24
- * Returning headers rather than setting them keeps the caller in charge of the
25
- * request: a transport that already has an `Authorization` header can decide
26
- * which wins, and core decides the same way — a request carrying a token is
27
- * answered by that token, and the proof is only consulted when there is none.
28
- */
29
- export type RequestAuthorizer = (request: RequestToAuthorize) => Promise<Record<string, string>>;
30
- /**
31
- * Turn a device cert into a {@link RequestAuthorizer}.
32
- *
33
- * The credential is validated once, here, rather than on the first request:
34
- * a malformed one otherwise surfaces as a 401 from whichever read happened to
35
- * run first, which reads as "the node refused me" and sends the caller looking
36
- * in the wrong place.
37
- */
38
- export declare function createProofAuthorizer(credential: ProofCredential): RequestAuthorizer;
39
- /** The path and query a proof signs, read from the parsed URL: the serialization
40
- * `fetch` puts on the request line, base-URL prefix and escapes included. */
41
- export declare function signedPath(url: string): string;
@@ -1,60 +0,0 @@
1
- /**
2
- * A device cert, as something every transport can authenticate a request with.
3
- *
4
- * {@link signRequestProof} signs one request. This wraps it in the shape the
5
- * rest of the SDK can consume without knowing what a proof is: a function from
6
- * `(method, path, body)` to the headers that request should carry. The HTTP
7
- * client, the SSE client and the admin client each take that function, so a
8
- * keyholder is authenticated the same way on all three and none of them holds a
9
- * second copy of the encoding.
10
- *
11
- * ## Why the authorizer, and not a token
12
- *
13
- * A bearer token is one value reused across requests, so `getAuthToken` can be
14
- * called once and its result cached. A proof commits to the method, the path and
15
- * the body, so it cannot be: every request needs its own, and the signature has
16
- * to be taken where those three are known. That is the whole reason this is a
17
- * per-request callback rather than another token provider.
18
- *
19
- * Measured against a node built from core `aecba573b`: reads answer `200` to a
20
- * proof with no session, `/sse` answers `200` and `/ws` upgrades `101`, and the
21
- * listings are caller-scoped — an account in one of the node's two namespaces is
22
- * shown one, and a WS subscribe naming a context it is not a member of is
23
- * refused while the one it is a member of is granted.
24
- */
25
- import { REQUEST_PROOF_HEADER, signRequestProof } from './request-proof.js';
26
- /**
27
- * Turn a device cert into a {@link RequestAuthorizer}.
28
- *
29
- * The credential is validated once, here, rather than on the first request:
30
- * a malformed one otherwise surfaces as a 401 from whichever read happened to
31
- * run first, which reads as "the node refused me" and sends the caller looking
32
- * in the wrong place.
33
- */
34
- export function createProofAuthorizer(credential) {
35
- if (!credential.credential) {
36
- throw new Error('a proof authorizer needs the `credential` from signDeviceCert');
37
- }
38
- if (!credential.deviceSecret && !credential.signer) {
39
- throw new Error('a proof authorizer needs either `deviceSecret` or `signer`');
40
- }
41
- return async ({ method, path, body }) => {
42
- const proof = await signRequestProof({
43
- method,
44
- path,
45
- body,
46
- credential: credential.credential,
47
- deviceSecret: credential.deviceSecret,
48
- signer: credential.signer,
49
- validForSeconds: credential.validForSeconds,
50
- });
51
- return { [REQUEST_PROOF_HEADER]: proof };
52
- };
53
- }
54
- /** The path and query a proof signs, read from the parsed URL: the serialization
55
- * `fetch` puts on the request line, base-URL prefix and escapes included. */
56
- export function signedPath(url) {
57
- // A relative URL resolves as `fetch` resolves it: against the page, if any.
58
- const parsed = new URL(url, globalThis.location?.href ?? 'http://localhost');
59
- return parsed.pathname + parsed.search;
60
- }
@@ -1,4 +0,0 @@
1
- export { signRequestProof, REQUEST_PROOF_HEADER } from './request-proof.js';
2
- export type { RequestProofInput } from './request-proof.js';
3
- export { createProofAuthorizer, signedPath } from './authorizer.js';
4
- export type { ProofCredential, RequestAuthorizer, RequestToAuthorize } from './authorizer.js';
@@ -1,2 +0,0 @@
1
- export { signRequestProof, REQUEST_PROOF_HEADER } from './request-proof.js';
2
- export { createProofAuthorizer, signedPath } from './authorizer.js';