@spfn/auth 0.3.0-beta.2 → 0.3.0-beta.20

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 (43) hide show
  1. package/README.md +1382 -23
  2. package/dist/client-proof.d.ts +45 -15
  3. package/dist/client-proof.js +198 -4
  4. package/dist/client-proof.js.map +1 -1
  5. package/dist/client.d.ts +92 -1
  6. package/dist/client.js +58 -0
  7. package/dist/client.js.map +1 -1
  8. package/dist/config.d.ts +302 -0
  9. package/dist/config.js +134 -4
  10. package/dist/config.js.map +1 -1
  11. package/dist/errors.d.ts +370 -3
  12. package/dist/errors.js +245 -2
  13. package/dist/errors.js.map +1 -1
  14. package/dist/index.d.ts +185 -2
  15. package/dist/index.js +256 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/machine-principals-BD4tnASp.d.ts +2739 -0
  18. package/dist/nextjs/api.js +350 -12
  19. package/dist/nextjs/api.js.map +1 -1
  20. package/dist/nextjs/client.d.ts +28 -1
  21. package/dist/nextjs/client.js +24 -3
  22. package/dist/nextjs/client.js.map +1 -1
  23. package/dist/nextjs/server.d.ts +173 -3
  24. package/dist/nextjs/server.js +372 -10
  25. package/dist/nextjs/server.js.map +1 -1
  26. package/dist/server.d.ts +3761 -414
  27. package/dist/server.js +5865 -1043
  28. package/dist/server.js.map +1 -1
  29. package/dist/{session-DTHahDQ9.d.ts → session-Dfwu5g2W.d.ts} +28 -1
  30. package/migrations/20260810112144_colorful_tomorrow_man/migration.sql +18 -0
  31. package/migrations/20260810112144_colorful_tomorrow_man/snapshot.json +3576 -0
  32. package/migrations/20260901091716_fine_arclight/migration.sql +21 -0
  33. package/migrations/20260901091716_fine_arclight/snapshot.json +3849 -0
  34. package/migrations/20260906155957_natural_moonstone/migration.sql +33 -0
  35. package/migrations/20260906155957_natural_moonstone/snapshot.json +4275 -0
  36. package/migrations/20260907020904_giant_eternals/migration.sql +21 -0
  37. package/migrations/20260907020904_giant_eternals/snapshot.json +4561 -0
  38. package/migrations/20260907044807_eminent_angel/migration.sql +2 -0
  39. package/migrations/20260907044807_eminent_angel/snapshot.json +4561 -0
  40. package/migrations/20260918083158_foamy_roughhouse/migration.sql +55 -0
  41. package/migrations/20260918083158_foamy_roughhouse/snapshot.json +5271 -0
  42. package/package.json +9 -6
  43. package/dist/authenticate-55LeXHqZ.d.ts +0 -1447
@@ -1,6 +1,7 @@
1
1
  import { KeyObject } from 'node:crypto';
2
2
  import { C as ClientProofRefusal } from './wire-version-CtzMKvBB.js';
3
3
  export { a as CLIENT_IDENTITY_HEADERS, b as CLIENT_KINDS, c as ClientIdentity, d as ClientKind, e as ClientProofErrorCode, S as SERVER_CONTRACT_HEADERS, f as applyServerContractHeaders, i as isAppKind, g as isContractVersionSupported, j as judgeClientIdentity, n as newHexId, r as readClientIdentity, s as serverContractHeaders } from './wire-version-CtzMKvBB.js';
4
+ import { CORE_TIME_OPERATION_ID } from '@spfn/core/server';
4
5
  import { MiddlewareHandler, Context } from 'hono';
5
6
 
6
7
  /**
@@ -397,8 +398,8 @@ declare function getClientProofReplayStore(): ClientProofReplayStore;
397
398
  */
398
399
 
399
400
  interface ContractOperation {
400
- id: 'auth.clientProof.handshake' | 'echo.send' | 'items.list' | 'auth.enroll.register' | 'auth.enroll.login' | 'auth.enroll.oauthNative' | 'auth.keys.rotate' | 'auth.keys.list' | 'auth.keys.revoke' | 'auth.keys.revokeAll';
401
- method: 'POST';
401
+ id: 'auth.clientProof.handshake' | 'echo.send' | 'items.list' | 'auth.enroll.register' | 'auth.enroll.login' | 'auth.enroll.oauthNative' | 'auth.keys.rotate' | 'auth.keys.list' | 'auth.keys.revoke' | 'auth.keys.revokeAll' | 'auth.device.start' | 'auth.device.poll' | 'auth.device.info' | 'auth.device.approve' | 'auth.device.deny' | typeof CORE_TIME_OPERATION_ID;
402
+ method: 'GET' | 'POST';
402
403
  path: string;
403
404
  /**
404
405
  * How a call is admitted. `clientProofV1` operations run the proof
@@ -408,8 +409,13 @@ interface ContractOperation {
408
409
  */
409
410
  authProfile: 'clientProofV1' | 'none';
410
411
  requiresSession: boolean;
411
- requestType: string;
412
- responseType: string;
412
+ /** Absent only when the operation has no request body. */
413
+ requestType?: string;
414
+ /**
415
+ * Absent only when the operation answers with no body — a 204, which has
416
+ * nothing for a consumer to decode and therefore names no type.
417
+ */
418
+ responseType?: string;
413
419
  summary: string;
414
420
  /**
415
421
  * The contract version this operation first appeared in. Required, so an
@@ -439,18 +445,42 @@ interface ContractOperation {
439
445
  */
440
446
  removedIn?: string;
441
447
  }
448
+ /** Validated projection of the imported core route contract. */
449
+ declare const IMPORTED_CORE_TIME_CONTRACT: {
450
+ readonly id: "core.time";
451
+ readonly method: "GET";
452
+ readonly path: string;
453
+ readonly authProfile: "none";
454
+ readonly requiresSession: false;
455
+ readonly sourceSince: string;
456
+ };
457
+ /**
458
+ * The core capability clientProofV1 needs before the client can mint a proof.
459
+ *
460
+ * Transport and admission fields come from core's exported route contract so
461
+ * auth cannot silently restate a different path or policy. `since` is the
462
+ * mobile-contract history, not core's package-contract history.
463
+ */
464
+ declare const CORE_PREREQUISITE_OPERATIONS: readonly ContractOperation[];
442
465
  declare const CONTRACT_OPERATIONS: readonly ContractOperation[];
443
466
  /**
444
- * The `/_auth` surface exported into the mobile contract: enrollment, login
445
- * and key rotation. These are ordinary SPFN REST routes, not canonical-JSON
446
- * operations — the dev handler never serves them, and their wire rules are
447
- * the `restOperations` section of the bundle, not `canonicalJson`.
448
- *
449
- * The three `authProfile: 'none'` operations are the unproven class: they are
450
- * accepted with neither proof headers nor a session header, because they are
451
- * how a client obtains a key in the first place. `auth.keys.rotate` requires
452
- * an authenticated caller (a clientProofV1 proof on this surface); an
453
- * unproven call to it is refused like any failed admission.
467
+ * The `/_auth` surface exported into the mobile contract: enrollment, login,
468
+ * key rotation and device-code login. These are ordinary SPFN REST routes, not
469
+ * canonical-JSON operations — the dev handler never serves them, and their wire
470
+ * rules are the `restOperations` section of the bundle, not `canonicalJson`.
471
+ *
472
+ * The `authProfile: 'none'` operations are the unproven class: they are accepted
473
+ * with neither proof headers nor a session header, because they are how a client
474
+ * obtains a key in the first place. `auth.keys.rotate` requires an authenticated
475
+ * caller (a clientProofV1 proof on this surface); an unproven call to it is
476
+ * refused like any failed admission.
477
+ *
478
+ * Device-code login is in both classes at once, because two devices run it. The
479
+ * device being let in calls `start` and `poll` unproven — it has no registered
480
+ * key to sign with, and getting one is the point of the flow. The approver calls
481
+ * `info`, `approve` and `deny` proven, from a device that is already signed in,
482
+ * which is what lets the server read the approving account from the caller
483
+ * rather than from the request body.
454
484
  */
455
485
  declare const AUTH_SURFACE_OPERATIONS: readonly ContractOperation[];
456
486
  /** The body is canonical JSON but not the declared request type. */
@@ -603,4 +633,4 @@ declare const CLIENT_IDENTITY_CONTEXT_KEY = "clientIdentity";
603
633
  */
604
634
  declare function createClientVersionMiddleware(): MiddlewareHandler;
605
635
 
606
- export { ABSENT_BODY_SHA256, AUTH_SURFACE_OPERATIONS, type Admission, CLIENT_IDENTITY_CONTEXT_KEY, CLIENT_PROOF_CONTENT_TYPE, CLIENT_PROOF_HEADERS, CLIENT_PROOF_PROFILE, CONTRACT_OPERATIONS, CONTROL_PREFIX, CONTROL_TOKEN_HEADER, CanonicalJsonError, type CanonicalJsonErrorCode, type CanonicalObject, type CanonicalValue, type ClientProofClock, type ClientProofContext, type ClientProofCredentials, type ClientProofDevHandler, type ClientProofDevHandlerOptions, type ClientProofGuardOptions, type ClientProofInput, ClientProofRefusal, type ClientProofReplayStore, ClientProofState, type ClientProofStateOptions, type ClientProofStats, type ContractItem, type ContractOperation, ContractTypeError, DEFAULT_REPLAY_WINDOW_MILLIS, DEFAULT_SESSION_TTL_MILLIS, DEV_CATALOGUE, DEV_MAX_LIMIT, type EchoRequest, type HandshakeRequest, type ListItemsRequest, MemoryReplayLedger, MemoryReplayStore, PROOF_SIGNATURE_BYTES, PROOF_SIGNATURE_HEX_LENGTH, ProofInputError, RedisReplayStore, TestClock, admitClientProofRequest, canonicalProofInput, clientProofRefusalResponse, configureClientProofReplayStore, createClientProofDevHandler, createClientProofGuard, createClientVersionMiddleware, decodeEchoRequest, decodeHandshakeRequest, decodeListItemsRequest, encodeCanonicalJson, encodeEchoResponse, encodeHandshakeResponse, encodeListItemsResponse, getClientProofReplayStore, isCanonicalBytes, isRequestContentType, parseCanonicalJson, parseClientProofPublicKey, readCredentials, replayLedgerKey, sha256Hex, signClientProof, systemClock, verifyClientProof };
636
+ export { ABSENT_BODY_SHA256, AUTH_SURFACE_OPERATIONS, type Admission, CLIENT_IDENTITY_CONTEXT_KEY, CLIENT_PROOF_CONTENT_TYPE, CLIENT_PROOF_HEADERS, CLIENT_PROOF_PROFILE, CONTRACT_OPERATIONS, CONTROL_PREFIX, CONTROL_TOKEN_HEADER, CORE_PREREQUISITE_OPERATIONS, CanonicalJsonError, type CanonicalJsonErrorCode, type CanonicalObject, type CanonicalValue, type ClientProofClock, type ClientProofContext, type ClientProofCredentials, type ClientProofDevHandler, type ClientProofDevHandlerOptions, type ClientProofGuardOptions, type ClientProofInput, ClientProofRefusal, type ClientProofReplayStore, ClientProofState, type ClientProofStateOptions, type ClientProofStats, type ContractItem, type ContractOperation, ContractTypeError, DEFAULT_REPLAY_WINDOW_MILLIS, DEFAULT_SESSION_TTL_MILLIS, DEV_CATALOGUE, DEV_MAX_LIMIT, type EchoRequest, type HandshakeRequest, IMPORTED_CORE_TIME_CONTRACT, type ListItemsRequest, MemoryReplayLedger, MemoryReplayStore, PROOF_SIGNATURE_BYTES, PROOF_SIGNATURE_HEX_LENGTH, ProofInputError, RedisReplayStore, TestClock, admitClientProofRequest, canonicalProofInput, clientProofRefusalResponse, configureClientProofReplayStore, createClientProofDevHandler, createClientProofGuard, createClientVersionMiddleware, decodeEchoRequest, decodeHandshakeRequest, decodeListItemsRequest, encodeCanonicalJson, encodeEchoResponse, encodeHandshakeResponse, encodeListItemsResponse, getClientProofReplayStore, isCanonicalBytes, isRequestContentType, parseCanonicalJson, parseClientProofPublicKey, readCredentials, replayLedgerKey, sha256Hex, signClientProof, systemClock, verifyClientProof };
@@ -935,6 +935,37 @@ function isRequestContentType(value) {
935
935
  }
936
936
 
937
937
  // src/server/client-proof/contract-types.ts
938
+ import {
939
+ CORE_TIME_OPERATION_ID,
940
+ CORE_TIME_ROUTE
941
+ } from "@spfn/core/server";
942
+ function importCoreTimeContract() {
943
+ const { method, path, contract } = CORE_TIME_ROUTE;
944
+ if (method !== "GET" || typeof path !== "string" || contract?.auth !== "none" || contract.requiresSession !== false || typeof contract.since !== "string") {
945
+ throw new Error("core.time does not match the clientProofV1 synchronization prerequisite");
946
+ }
947
+ return {
948
+ id: CORE_TIME_OPERATION_ID,
949
+ method,
950
+ path,
951
+ authProfile: contract.auth,
952
+ requiresSession: contract.requiresSession,
953
+ sourceSince: contract.since
954
+ };
955
+ }
956
+ var IMPORTED_CORE_TIME_CONTRACT = importCoreTimeContract();
957
+ var CORE_PREREQUISITE_OPERATIONS = [
958
+ {
959
+ id: IMPORTED_CORE_TIME_CONTRACT.id,
960
+ method: IMPORTED_CORE_TIME_CONTRACT.method,
961
+ path: IMPORTED_CORE_TIME_CONTRACT.path,
962
+ authProfile: IMPORTED_CORE_TIME_CONTRACT.authProfile,
963
+ requiresSession: IMPORTED_CORE_TIME_CONTRACT.requiresSession,
964
+ responseType: "ServerTimeResponse",
965
+ summary: "Returns the server epoch used to timestamp clientProofV1 proofs.",
966
+ since: "0.9.0"
967
+ }
968
+ ];
938
969
  var CONTRACT_OPERATIONS = [
939
970
  {
940
971
  id: "auth.clientProof.handshake",
@@ -1047,6 +1078,60 @@ var AUTH_SURFACE_OPERATIONS = [
1047
1078
  responseType: "RevokeAllKeysResponse",
1048
1079
  summary: "Revokes every key the caller has, sparing the calling device unless asked otherwise.",
1049
1080
  since: "0.4.1"
1081
+ },
1082
+ {
1083
+ id: "auth.device.start",
1084
+ method: "POST",
1085
+ path: "/_auth/device/start",
1086
+ authProfile: "none",
1087
+ requiresSession: false,
1088
+ requestType: "StartDeviceAuthRequest",
1089
+ responseType: "StartDeviceAuthResponse",
1090
+ summary: "Parks a new device's public key and returns the codes it shows and polls with.",
1091
+ since: "0.10.0"
1092
+ },
1093
+ {
1094
+ id: "auth.device.poll",
1095
+ method: "POST",
1096
+ path: "/_auth/device/poll",
1097
+ authProfile: "none",
1098
+ requiresSession: false,
1099
+ requestType: "PollDeviceAuthRequest",
1100
+ responseType: "PollDeviceAuthResponse",
1101
+ summary: "Asks whether the request has been answered; the approved answer is the login it produced.",
1102
+ since: "0.10.0"
1103
+ },
1104
+ {
1105
+ id: "auth.device.info",
1106
+ method: "POST",
1107
+ path: "/_auth/device/info",
1108
+ authProfile: "clientProofV1",
1109
+ requiresSession: false,
1110
+ requestType: "DeviceAuthInfoRequest",
1111
+ responseType: "DeviceAuthInfoResponse",
1112
+ summary: "Describes the device waiting on a user code, so the approver can recognise it before deciding.",
1113
+ since: "0.10.0"
1114
+ },
1115
+ {
1116
+ id: "auth.device.approve",
1117
+ method: "POST",
1118
+ path: "/_auth/device/approve",
1119
+ authProfile: "clientProofV1",
1120
+ requiresSession: false,
1121
+ requestType: "ApproveDeviceAuthRequest",
1122
+ responseType: "DeviceAuthInfoResponse",
1123
+ summary: "Lets the waiting device in, answering with the device it just let in.",
1124
+ since: "0.10.0"
1125
+ },
1126
+ {
1127
+ id: "auth.device.deny",
1128
+ method: "POST",
1129
+ path: "/_auth/device/deny",
1130
+ authProfile: "clientProofV1",
1131
+ requiresSession: false,
1132
+ requestType: "DenyDeviceAuthRequest",
1133
+ summary: "Refuses the waiting device. Answers 204 with no body, so it names no response type.",
1134
+ since: "0.10.0"
1050
1135
  }
1051
1136
  ];
1052
1137
  var ContractTypeError = class extends Error {
@@ -1287,9 +1372,14 @@ function answer(status, value) {
1287
1372
 
1288
1373
  // src/server/client-proof/contract-bundle.ts
1289
1374
  import { createHash as createHash2 } from "crypto";
1375
+ import {
1376
+ CORE_TIME_OPERATION_ID as CORE_TIME_OPERATION_ID2,
1377
+ ServerTimeResponseSchema
1378
+ } from "@spfn/core/server";
1290
1379
 
1291
1380
  // src/server/types.ts
1292
1381
  var KEY_ALGORITHM = ["ES256", "RS256"];
1382
+ var KEY_PLATFORM = ["ios", "android", "web", "desktop"];
1293
1383
 
1294
1384
  // src/server/client-proof/wire-headers.ts
1295
1385
  var CLIENT_IDENTITY_HEADERS = {
@@ -1307,16 +1397,34 @@ function isAppKind(kind) {
1307
1397
  }
1308
1398
 
1309
1399
  // src/server/client-proof/contract-bundle.ts
1310
- var CONTRACT_VERSION = "0.8.0";
1400
+ var CONTRACT_VERSION = "0.10.1";
1311
1401
  var CONTRACT_MAJOR = 0;
1312
- var CONTRACT_SUPPORTED_RANGE = ">=0.8.0 <0.9.0";
1402
+ var CONTRACT_SUPPORTED_RANGE = ">=0.10.0 <0.11.0";
1313
1403
  function required(name, type) {
1314
1404
  return { name, type, optional: false };
1315
1405
  }
1316
1406
  function optional(name, type) {
1317
1407
  return { name, type, optional: true };
1318
1408
  }
1409
+ function coreTimeResponseDeclaration() {
1410
+ if (ServerTimeResponseSchema.type !== "object" || ServerTimeResponseSchema.additionalProperties !== false) {
1411
+ throw new Error("core.time response must remain a closed object");
1412
+ }
1413
+ const requiredFields = new Set(ServerTimeResponseSchema.required);
1414
+ const fields = Object.entries(ServerTimeResponseSchema.properties).map(([name, schema]) => {
1415
+ if (schema.type !== "integer") {
1416
+ throw new Error(`core.time response field ${name} is outside the mobile type grammar`);
1417
+ }
1418
+ return {
1419
+ name,
1420
+ type: "integer",
1421
+ optional: !requiredFields.has(name)
1422
+ };
1423
+ });
1424
+ return { name: "ServerTimeResponse", fields };
1425
+ }
1319
1426
  var CONTRACT_TYPES = [
1427
+ coreTimeResponseDeclaration(),
1320
1428
  {
1321
1429
  name: "HandshakeRequest",
1322
1430
  fields: [
@@ -1462,7 +1570,7 @@ var CONTRACT_TYPES = [
1462
1570
  fields: [
1463
1571
  required("keyId", "string"),
1464
1572
  optional("deviceName", "string"),
1465
- optional("platform", "string"),
1573
+ optional("platform", "KeyPlatform"),
1466
1574
  required("algorithm", "KeyAlgorithm"),
1467
1575
  required("fingerprintPrefix", "string"),
1468
1576
  required("createdAtMillis", "integer"),
@@ -1504,10 +1612,94 @@ var CONTRACT_TYPES = [
1504
1612
  required("revokedCount", "integer"),
1505
1613
  required("currentKeyRevoked", "boolean")
1506
1614
  ]
1615
+ },
1616
+ {
1617
+ name: "StartDeviceAuthRequest",
1618
+ fields: [
1619
+ required("publicKey", "string"),
1620
+ required("keyId", "string"),
1621
+ required("fingerprint", "string"),
1622
+ optional("algorithm", "KeyAlgorithm"),
1623
+ optional("deviceName", "string"),
1624
+ optional("platform", "KeyPlatform")
1625
+ ]
1626
+ },
1627
+ {
1628
+ name: "StartDeviceAuthResponse",
1629
+ fields: [
1630
+ required("deviceCode", "string"),
1631
+ required("userCode", "string"),
1632
+ required("expiresAtMillis", "integer"),
1633
+ required("intervalMillis", "integer")
1634
+ ]
1635
+ },
1636
+ {
1637
+ name: "PollDeviceAuthRequest",
1638
+ fields: [
1639
+ required("deviceCode", "string")
1640
+ ]
1641
+ },
1642
+ /**
1643
+ * The poll union, flattened into the one shape this grammar can carry.
1644
+ *
1645
+ * `status` is the discriminant and the only required field; everything else
1646
+ * belongs to one branch and is therefore optional. `intervalMillis` is the
1647
+ * pending branch, and the five after it are the approved branch — the same
1648
+ * fields `LoginResponse` carries, because an approved poll is the login the
1649
+ * approval produced. `deviceAuthorization.pollStatusRule` states the pairing
1650
+ * the grammar cannot.
1651
+ */
1652
+ {
1653
+ name: "PollDeviceAuthResponse",
1654
+ fields: [
1655
+ required("status", "DeviceAuthPollStatus"),
1656
+ optional("intervalMillis", "integer"),
1657
+ optional("userId", "string"),
1658
+ optional("publicId", "string"),
1659
+ optional("email", "string"),
1660
+ optional("phone", "string"),
1661
+ optional("passwordChangeRequired", "boolean")
1662
+ ]
1663
+ },
1664
+ /**
1665
+ * Info, approve and deny each declare their own request type although all
1666
+ * three carry nothing but `userCode`. An operation's request shape is its
1667
+ * own: a field added to one of them later must not appear on the other two
1668
+ * by accident, which is what a shared type would do.
1669
+ */
1670
+ {
1671
+ name: "DeviceAuthInfoRequest",
1672
+ fields: [
1673
+ required("userCode", "string")
1674
+ ]
1675
+ },
1676
+ {
1677
+ name: "DeviceAuthInfoResponse",
1678
+ fields: [
1679
+ optional("deviceName", "string"),
1680
+ optional("platform", "KeyPlatform"),
1681
+ required("fingerprintPrefix", "string"),
1682
+ required("requestedAtMillis", "integer"),
1683
+ required("expiresAtMillis", "integer")
1684
+ ]
1685
+ },
1686
+ {
1687
+ name: "ApproveDeviceAuthRequest",
1688
+ fields: [
1689
+ required("userCode", "string")
1690
+ ]
1691
+ },
1692
+ {
1693
+ name: "DenyDeviceAuthRequest",
1694
+ fields: [
1695
+ required("userCode", "string")
1696
+ ]
1507
1697
  }
1508
1698
  ];
1509
1699
  var CONTRACT_ENUMS = [
1510
- { name: "KeyAlgorithm", values: [...KEY_ALGORITHM] }
1700
+ { name: "KeyAlgorithm", values: [...KEY_ALGORITHM] },
1701
+ { name: "KeyPlatform", values: [...KEY_PLATFORM] },
1702
+ { name: "DeviceAuthPollStatus", values: ["pending", "approved"] }
1511
1703
  ];
1512
1704
  var BUNDLE_FILENAME = "spfn-mobile-contract.json";
1513
1705
  var BUNDLE_REPO_PATH = `contracts/mobile/${BUNDLE_FILENAME}`;
@@ -1790,6 +1982,7 @@ export {
1790
1982
  CONTRACT_OPERATIONS,
1791
1983
  CONTROL_PREFIX,
1792
1984
  CONTROL_TOKEN_HEADER,
1985
+ CORE_PREREQUISITE_OPERATIONS,
1793
1986
  CanonicalJsonError,
1794
1987
  ClientProofRefusal,
1795
1988
  ClientProofState,
@@ -1798,6 +1991,7 @@ export {
1798
1991
  DEFAULT_SESSION_TTL_MILLIS,
1799
1992
  DEV_CATALOGUE,
1800
1993
  DEV_MAX_LIMIT,
1994
+ IMPORTED_CORE_TIME_CONTRACT,
1801
1995
  MemoryReplayLedger,
1802
1996
  MemoryReplayStore,
1803
1997
  PROOF_SIGNATURE_BYTES,