@unicitylabs/sphere-sdk 0.7.1 → 0.7.2

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.
package/README.md CHANGED
@@ -159,64 +159,6 @@ sphere.setPriceProvider(createPriceProvider({
159
159
  }));
160
160
  ```
161
161
 
162
- ## Building a Sphere-authenticated backend
163
-
164
- If your service needs to authenticate users via their Sphere wallet, use `verifySphereAuth`. It performs signature verification and identity derivation in one misuse-resistant call.
165
-
166
- ```typescript
167
- import { randomBytes } from 'crypto';
168
- import { verifySphereAuth, AuthVerificationError } from '@unicitylabs/sphere-sdk';
169
-
170
- const challenges = new Map<string, { challenge: string; expiresAt: number }>();
171
-
172
- // POST /auth/challenge — issue a one-shot nonce challenge
173
- server.post('/auth/challenge', async (req) => {
174
- const { chainPubkey } = req.body;
175
- const nonce = randomBytes(16).toString('hex');
176
- const challenge = `Sign in to MyApp\nPubkey: ${chainPubkey}\nNonce: ${nonce}\nIssued At: ${new Date().toISOString()}`;
177
- challenges.set(chainPubkey, { challenge, expiresAt: Date.now() + 5 * 60_000 });
178
- return { challenge };
179
- });
180
-
181
- // POST /auth/verify — turn a signed challenge into a session
182
- server.post('/auth/verify', async (req, reply) => {
183
- const { chainPubkey, signature } = req.body;
184
- const stored = challenges.get(chainPubkey);
185
- if (!stored || Date.now() > stored.expiresAt) {
186
- return reply.status(401).send({ error: 'No valid challenge — request a new one' });
187
- }
188
- challenges.delete(chainPubkey); // one-shot
189
-
190
- try {
191
- const { chainPubkey: pk, directAddress } = await verifySphereAuth({
192
- challenge: stored.challenge,
193
- signature,
194
- chainPubkey,
195
- });
196
- // pk and directAddress are now safe to use as user identifiers —
197
- // neither is a client claim, both are derived from cryptographic proof.
198
- const token = server.jwt.sign({ sub: pk, addr: directAddress }, { expiresIn: '24h' });
199
- return { token };
200
- } catch (err) {
201
- if (err instanceof AuthVerificationError) {
202
- return reply.status(401).send({ error: err.message, code: err.code });
203
- }
204
- throw err;
205
- }
206
- });
207
- ```
208
-
209
- ### Why not accept `directAddress` from the client?
210
-
211
- `verifySphereAuth` deliberately does not take a `directAddress` parameter. The signature proves that the client owns the privkey to the pubkey they presented — but it does NOT prove that the pubkey corresponds to any particular `directAddress`. If you accept `directAddress` as a separate body field and use it as the user-identifier key, an attacker can pre-bind any unregistered address to their own pubkey — locking the rightful owner out forever. `verifySphereAuth` derives `directAddress` from `chainPubkey` server-side instead, eliminating the bug class.
212
-
213
- If you need just the address derivation in a custom flow, use the primitive directly:
214
-
215
- ```typescript
216
- import { computeDirectAddressFromChainPubkey } from '@unicitylabs/sphere-sdk';
217
- const addr = await computeDirectAddressFromChainPubkey(chainPubkey);
218
- ```
219
-
220
162
  ## Testnet Faucet
221
163
 
222
164
  To get test tokens on testnet, you **must first register a nametag**:
@@ -881,7 +881,6 @@ __export(core_exports, {
881
881
  base58Encode: () => base58Encode,
882
882
  bytesToHex: () => bytesToHex3,
883
883
  checkNetworkHealth: () => checkNetworkHealth,
884
- computeDirectAddressFromChainPubkey: () => computeDirectAddressFromChainPubkey,
885
884
  computeHash160: () => computeHash160,
886
885
  convertBits: () => convertBits,
887
886
  createAddress: () => createAddress,
@@ -939,6 +938,7 @@ __export(core_exports, {
939
938
  randomBytes: () => randomBytes2,
940
939
  randomHex: () => randomHex,
941
940
  randomUUID: () => randomUUID,
941
+ recoverPubkeyFromSignature: () => recoverPubkeyFromSignature,
942
942
  ripemd160: () => ripemd160,
943
943
  scanAddressesImpl: () => scanAddressesImpl,
944
944
  serializeEncrypted: () => serializeEncrypted,
@@ -4918,6 +4918,27 @@ function verifySignedMessage(message, signature, expectedPubkey) {
4918
4918
  return false;
4919
4919
  }
4920
4920
  }
4921
+ function recoverPubkeyFromSignature(message, signature) {
4922
+ if (signature.length !== 130) {
4923
+ throw new SphereError(
4924
+ `Invalid signature length: expected 130 hex chars, got ${signature.length}`,
4925
+ "SIGNING_ERROR"
4926
+ );
4927
+ }
4928
+ const v = parseInt(signature.slice(0, 2), 16) - 31;
4929
+ const r = signature.slice(2, 66);
4930
+ const s = signature.slice(66, 130);
4931
+ if (v < 0 || v > 3) {
4932
+ throw new SphereError(
4933
+ `Invalid recovery byte: v=${v} out of range [0..3]`,
4934
+ "SIGNING_ERROR"
4935
+ );
4936
+ }
4937
+ const hashHex = hashSignMessage(message);
4938
+ const hashBytes = Buffer.from(hashHex, "hex");
4939
+ const recovered = ec.recoverPubKey(hashBytes, { r, s }, v);
4940
+ return recovered.encode("hex", true);
4941
+ }
4921
4942
 
4922
4943
  // l1/crypto.ts
4923
4944
  var import_crypto_js3 = __toESM(require("crypto-js"), 1);
@@ -27450,42 +27471,27 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
27450
27471
 
27451
27472
  // core/Sphere.ts
27452
27473
  var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign/SigningService");
27453
- var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
27454
-
27455
- // core/address-derivation.ts
27456
27474
  var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
27457
27475
  var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
27458
27476
  var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
27459
- var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
27460
- var COMPRESSED_PUBKEY_RE = /^(02|03)[0-9a-fA-F]{64}$/;
27461
- async function computeDirectAddressFromChainPubkey(chainPubkey) {
27462
- if (typeof chainPubkey !== "string" || !COMPRESSED_PUBKEY_RE.test(chainPubkey)) {
27463
- throw new Error(
27464
- `computeDirectAddressFromChainPubkey: chainPubkey must be 66-char hex with 02/03 prefix, got "${String(chainPubkey).slice(0, 12)}..."`
27465
- );
27466
- }
27467
- const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
27468
- const tokenType = new import_TokenType5.TokenType(tokenTypeBytes);
27469
- const publicKeyBytes = Buffer.from(chainPubkey, "hex");
27470
- const predicateRef = await import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
27471
- tokenType,
27472
- "secp256k1",
27473
- publicKeyBytes,
27474
- import_HashAlgorithm7.HashAlgorithm.SHA256
27475
- );
27476
- return (await predicateRef.toAddress()).toString();
27477
- }
27478
-
27479
- // core/Sphere.ts
27477
+ var import_nostr_js_sdk5 = require("@unicitylabs/nostr-js-sdk");
27480
27478
  function isValidNametag2(nametag) {
27481
27479
  if ((0, import_nostr_js_sdk5.isPhoneNumber)(nametag)) return true;
27482
27480
  return /^[a-z0-9_-]{3,20}$/.test(nametag);
27483
27481
  }
27482
+ var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
27484
27483
  async function deriveL3PredicateAddress(privateKey) {
27485
27484
  const secret = Buffer.from(privateKey, "hex");
27486
27485
  const signingService = await import_SigningService2.SigningService.createFromSecret(secret);
27487
- const pubkeyHex = Buffer.from(signingService.publicKey).toString("hex");
27488
- return computeDirectAddressFromChainPubkey(pubkeyHex);
27486
+ const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
27487
+ const tokenType = new import_TokenType5.TokenType(tokenTypeBytes);
27488
+ const predicateRef = import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
27489
+ tokenType,
27490
+ signingService.algorithm,
27491
+ signingService.publicKey,
27492
+ import_HashAlgorithm7.HashAlgorithm.SHA256
27493
+ );
27494
+ return (await (await predicateRef).toAddress()).toString();
27489
27495
  }
27490
27496
  var Sphere = class _Sphere {
27491
27497
  // Singleton
@@ -30870,7 +30876,6 @@ async function runCustomCheck(name, checkFn, timeoutMs) {
30870
30876
  base58Encode,
30871
30877
  bytesToHex,
30872
30878
  checkNetworkHealth,
30873
- computeDirectAddressFromChainPubkey,
30874
30879
  computeHash160,
30875
30880
  convertBits,
30876
30881
  createAddress,
@@ -30928,6 +30933,7 @@ async function runCustomCheck(name, checkFn, timeoutMs) {
30928
30933
  randomBytes,
30929
30934
  randomHex,
30930
30935
  randomUUID,
30936
+ recoverPubkeyFromSignature,
30931
30937
  ripemd160,
30932
30938
  scanAddressesImpl,
30933
30939
  serializeEncrypted,