@soropass/core 0.1.2 → 0.2.1

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 (67) hide show
  1. package/README.md +93 -0
  2. package/dist/{checkAuth-C2AcXqFe.d.cts → checkAuth-B5ObsEsP.d.cts} +11 -7
  3. package/dist/{checkAuth-C2AcXqFe.d.ts → checkAuth-B5ObsEsP.d.ts} +11 -7
  4. package/dist/{chunk-AHFGTIBY.js → chunk-2KLSTE4E.js} +16 -4
  5. package/dist/chunk-2KLSTE4E.js.map +1 -0
  6. package/dist/{chunk-KDBIRRF7.js → chunk-42ETVY6V.js} +3 -3
  7. package/dist/{chunk-KDBIRRF7.js.map → chunk-42ETVY6V.js.map} +1 -1
  8. package/dist/{chunk-ZQRFIL4P.js → chunk-ODXQCLZL.js} +18 -6
  9. package/dist/chunk-ODXQCLZL.js.map +1 -0
  10. package/dist/{chunk-MMB4ZIW3.js → chunk-OHKPNVB3.js} +13 -5
  11. package/dist/chunk-OHKPNVB3.js.map +1 -0
  12. package/dist/{chunk-BLFCRWCT.js → chunk-QREICNIW.js} +3 -3
  13. package/dist/{chunk-BLFCRWCT.js.map → chunk-QREICNIW.js.map} +1 -1
  14. package/dist/{chunk-W75OQKHR.js → chunk-RFMHMWTY.js} +5 -3
  15. package/dist/chunk-RFMHMWTY.js.map +1 -0
  16. package/dist/{chunk-FIRTUNWF.js → chunk-XTLWH3VQ.js} +26 -5
  17. package/dist/chunk-XTLWH3VQ.js.map +1 -0
  18. package/dist/connect.cjs +14 -2
  19. package/dist/connect.cjs.map +1 -1
  20. package/dist/connect.d.cts +11 -1
  21. package/dist/connect.d.ts +11 -1
  22. package/dist/connect.js +1 -1
  23. package/dist/create.cjs +14 -4
  24. package/dist/create.cjs.map +1 -1
  25. package/dist/create.d.cts +20 -12
  26. package/dist/create.d.ts +20 -12
  27. package/dist/create.js +2 -2
  28. package/dist/index.cjs +77 -13
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +9 -9
  31. package/dist/index.d.ts +9 -9
  32. package/dist/index.js +20 -11
  33. package/dist/index.js.map +1 -1
  34. package/dist/{recover-0wbOXDjR.d.cts → recover-BOq0DQaU.d.cts} +1 -1
  35. package/dist/{recover-COnsxhYk.d.ts → recover-CL_jZ2kN.d.ts} +1 -1
  36. package/dist/recover.cjs +24 -3
  37. package/dist/recover.cjs.map +1 -1
  38. package/dist/recover.d.cts +4 -4
  39. package/dist/recover.d.ts +4 -4
  40. package/dist/recover.js +3 -3
  41. package/dist/{sign-B4JV2Inn.d.ts → sign-O5d7Moma.d.cts} +15 -0
  42. package/dist/{sign-B4JV2Inn.d.cts → sign-O5d7Moma.d.ts} +15 -0
  43. package/dist/sign.cjs +39 -6
  44. package/dist/sign.cjs.map +1 -1
  45. package/dist/sign.d.cts +27 -14
  46. package/dist/sign.d.ts +27 -14
  47. package/dist/sign.js +3 -3
  48. package/dist/{submit-BsKXOCkL.d.cts → submit-BKogb6Y8.d.cts} +2 -2
  49. package/dist/{submit-CZt8fNRf.d.ts → submit-CKJp7XgZ.d.ts} +2 -2
  50. package/dist/testing.cjs +46 -8
  51. package/dist/testing.cjs.map +1 -1
  52. package/dist/testing.d.cts +8 -5
  53. package/dist/testing.d.ts +8 -5
  54. package/dist/testing.js +8 -5
  55. package/dist/testing.js.map +1 -1
  56. package/dist/{types-C3VIw5Iu.d.ts → types-BJsL-q2B.d.ts} +3 -3
  57. package/dist/{types-DLcXrud8.d.cts → types-B_pVAg-T.d.cts} +3 -3
  58. package/dist/{types--WNl-Sda.d.cts → types-CJluKV7s.d.cts} +8 -0
  59. package/dist/{types--WNl-Sda.d.ts → types-CJluKV7s.d.ts} +8 -0
  60. package/dist/types.d.cts +4 -4
  61. package/dist/types.d.ts +4 -4
  62. package/package.json +23 -2
  63. package/dist/chunk-AHFGTIBY.js.map +0 -1
  64. package/dist/chunk-FIRTUNWF.js.map +0 -1
  65. package/dist/chunk-MMB4ZIW3.js.map +0 -1
  66. package/dist/chunk-W75OQKHR.js.map +0 -1
  67. package/dist/chunk-ZQRFIL4P.js.map +0 -1
package/dist/create.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { P as PasskeyCredential } from './types-C3VIw5Iu.js';
2
- export { d as assertES256, e as assertUserActivation } from './types-C3VIw5Iu.js';
3
- import { A as AccountDeployer, W as WebAuthnClient, C as CredentialStorage } from './types--WNl-Sda.js';
4
- export { g as buildCreateOptions } from './types--WNl-Sda.js';
5
- import './checkAuth-C2AcXqFe.js';
1
+ import { P as PasskeyCredential } from './types-BJsL-q2B.js';
2
+ export { d as assertES256, e as assertUserActivation } from './types-BJsL-q2B.js';
3
+ import { A as AccountDeployer, W as WebAuthnClient, C as CredentialStorage } from './types-CJluKV7s.js';
4
+ export { g as buildCreateOptions } from './types-CJluKV7s.js';
5
+ import './checkAuth-B5ObsEsP.js';
6
6
  import '@stellar/stellar-sdk';
7
- import './sign-B4JV2Inn.js';
7
+ import './sign-O5d7Moma.js';
8
8
 
9
9
  interface CreatePasskeyOptions {
10
10
  rpId: string;
@@ -66,17 +66,25 @@ declare function extractPublicKeyFromAttestationObject(attestationObject: Uint8A
66
66
  interface DeriveAccountAddressOptions {
67
67
  /** The AccountFactory contract C-address — the deployer. */
68
68
  factoryContractId: string;
69
- /** The credential-id bytes the factory salts by SHA-256 (utf-8 of the base64url id in our factory). */
69
+ /** The credential-id bytes the factory salts (utf-8 of the base64url id in our factory). */
70
70
  credentialId: Uint8Array;
71
+ /**
72
+ * SEC-1 (65-byte) public key of the founding passkey. The v0.2 factory binds
73
+ * it into the deploy salt (`sha256(credential_id ‖ public_key)`) so a public
74
+ * credential id alone cannot be used to squat a derived address.
75
+ */
76
+ publicKey: Uint8Array;
71
77
  networkPassphrase: string;
72
78
  }
73
79
  /**
74
80
  * Derive the deterministic C-address of the `webauthn-account` our AccountFactory
75
- * deploys for a passkey — with NO network round-trip. The factory salts by
76
- * `sha256(credential_id)` and deploys with `deployer().with_current_contract(salt)`.
77
- * Verified against the on-chain `factoryDeployProof` in `contracts/deployments.json`
78
- * (credential `democred` `CAGWE36M…`). For the passkey-kit v1 smart-wallet, use
79
- * {@link deriveSmartWalletAddress} instead (different deployer + salt-input scheme).
81
+ * deploys for a passkey — with NO network round-trip. The v0.2 factory salts by
82
+ * `sha256(credential_id ‖ public_key)` and deploys with
83
+ * `deployer().with_current_contract(salt)`. Binding the public key is the F1
84
+ * fix: a credential id is public (the factory emits it), so salting by it alone
85
+ * let anyone pre-deploy at a victim's address with their own key. For the
86
+ * passkey-kit v1 smart-wallet, use {@link deriveSmartWalletAddress} instead
87
+ * (different deployer + salt-input scheme).
80
88
  */
81
89
  declare function deriveAccountAddress(options: DeriveAccountAddressOptions): string;
82
90
  interface DeriveSmartWalletAddressOptions {
package/dist/create.js CHANGED
@@ -1,5 +1,5 @@
1
- export { deriveAccountAddress, deriveSmartWalletAddress } from './chunk-MMB4ZIW3.js';
2
- export { buildCreateOptions, coseKeyToSec1, createPasskey, extractPublicKeyFromAttestationObject, extractPublicKeyFromAuthData, registerPasskey } from './chunk-W75OQKHR.js';
1
+ export { deriveAccountAddress, deriveSmartWalletAddress } from './chunk-OHKPNVB3.js';
2
+ export { buildCreateOptions, coseKeyToSec1, createPasskey, extractPublicKeyFromAttestationObject, extractPublicKeyFromAuthData, registerPasskey } from './chunk-RFMHMWTY.js';
3
3
  import './chunk-JNJ2FY45.js';
4
4
  export { browserWebAuthnClient, defaultCredentialStorage } from './chunk-LB4HOTLV.js';
5
5
  export { assertES256, assertUserActivation } from './chunk-TPBBZNPP.js';
package/dist/index.cjs CHANGED
@@ -91,7 +91,9 @@ function buildCreateOptions(input) {
91
91
  authenticatorSelection: {
92
92
  residentKey,
93
93
  requireResidentKey: residentKey === "required",
94
- userVerification: input.userVerification ?? "preferred"
94
+ // The v0.2 account requires the User-Verified flag at sign time, so
95
+ // register with 'required' to enroll authenticators that can produce it.
96
+ userVerification: input.userVerification ?? "required"
95
97
  },
96
98
  attestation: "none",
97
99
  timeout: input.timeout ?? 6e4
@@ -534,14 +536,22 @@ function contractIdFromDeployerSalt(deployer, salt, networkPassphrase) {
534
536
  return stellarSdk.StrKey.encodeContract(stellarSdk.hash(preimage.toXDR()));
535
537
  }
536
538
  function deriveAccountAddress(options) {
537
- const { factoryContractId, credentialId, networkPassphrase } = options;
539
+ const { factoryContractId, credentialId, publicKey, networkPassphrase } = options;
538
540
  if (credentialId.length === 0) {
539
541
  throw new KitError("CONTRACT_AUTH_FAILED", "deriveAccountAddress: empty credential id");
540
542
  }
543
+ if (publicKey.length !== 65) {
544
+ throw new KitError(
545
+ "CONTRACT_AUTH_FAILED",
546
+ `deriveAccountAddress: public key must be 65-byte SEC-1 (got ${String(publicKey.length)})`
547
+ );
548
+ }
549
+ const saltInput = new Uint8Array(credentialId.length + publicKey.length);
550
+ saltInput.set(credentialId, 0);
551
+ saltInput.set(publicKey, credentialId.length);
541
552
  return contractIdFromDeployerSalt(
542
553
  factoryContractId,
543
- stellarSdk.hash(Buffer.from(credentialId)),
544
- // e.crypto().sha256(credential_id)
554
+ stellarSdk.hash(Buffer.from(saltInput)),
545
555
  networkPassphrase
546
556
  );
547
557
  }
@@ -605,13 +615,16 @@ function browserPasskeySigner(options) {
605
615
  rpId: options.rpId,
606
616
  challenge: base64UrlToBytes(challenge),
607
617
  allowCredentials: options.allowCredentials ?? [],
608
- userVerification: options.userVerification ?? "preferred"
618
+ // The v0.2 account requires the User-Verified flag, so default to
619
+ // 'required'; a signature without UV fails on-chain.
620
+ userVerification: options.userVerification ?? "required"
609
621
  });
610
622
  return {
611
623
  authenticatorData: assertion.authenticatorData,
612
624
  clientDataJSON: assertion.clientDataJSON,
613
625
  signature: assertion.signature,
614
- credentialId: assertion.rawId
626
+ credentialId: assertion.rawId,
627
+ publicKey: options.publicKey
615
628
  };
616
629
  };
617
630
  }
@@ -634,10 +647,24 @@ function authEntryChallengeBytes(entry, networkPassphrase) {
634
647
  function authEntryChallenge(entry, networkPassphrase) {
635
648
  return bytesToBase64Url(authEntryChallengeBytes(entry, networkPassphrase));
636
649
  }
637
- function applyAssertionToEntry(entry, assertion) {
638
- entry.credentials().address().signature(buildSignatureScVal(assertion));
650
+ function applyAssertionToEntry(entry, assertion, publicKey) {
651
+ entry.credentials().address().signature(buildSingleSignerSignatureScVal(assertion, publicKey));
639
652
  return entry;
640
653
  }
654
+ function buildSingleSignerSignatureScVal(assertion, publicKey) {
655
+ if (publicKey.length !== 65) {
656
+ throw new KitError(
657
+ "CONTRACT_AUTH_FAILED",
658
+ `single-signer signature needs a 65-byte SEC-1 public key (got ${String(publicKey.length)})`
659
+ );
660
+ }
661
+ return stellarSdk.xdr.ScVal.scvMap([
662
+ bytesField("authenticator_data", assertion.authenticatorData),
663
+ bytesField("client_data_json", assertion.clientDataJSON),
664
+ bytesField("public_key", publicKey),
665
+ bytesField("signature", assertion.signature)
666
+ ]);
667
+ }
641
668
  function buildSignatureScVal(assertion) {
642
669
  return stellarSdk.xdr.ScVal.scvMap([
643
670
  bytesField("authenticator_data", assertion.authenticatorData),
@@ -790,7 +817,14 @@ async function signEntryInPlace(entry, options) {
790
817
  if (options.target === "smart-wallet") {
791
818
  applyAssertionToSmartWalletEntry(entry, normalized);
792
819
  } else {
793
- applyAssertionToEntry(entry, normalized);
820
+ const publicKey = options.publicKey ?? assertion.publicKey;
821
+ if (publicKey === void 0) {
822
+ throw new KitError(
823
+ "CONTRACT_AUTH_FAILED",
824
+ "single-signer signing requires the signer public key: set SorobanSignOptions.publicKey or return publicKey from the signer"
825
+ );
826
+ }
827
+ applyAssertionToEntry(entry, normalized, publicKey);
794
828
  }
795
829
  }
796
830
  async function signAuthEntry(entryXdr, options) {
@@ -926,17 +960,26 @@ function verifySecp256r1StructMap(structMap, publicKey, signaturePayload) {
926
960
  }
927
961
  const digest = sha256.sha256(concatBytes(authenticatorData, sha256.sha256(clientDataJSON)));
928
962
  const signatureValid = nist.p256.verify(signature, digest, publicKey, {
929
- lowS: false,
963
+ lowS: true,
930
964
  format: "compact"
931
965
  });
932
966
  return { ok: challengeBound && signatureValid, challengeBound, signatureValid };
933
967
  }
968
+ function bytesEqual(a, b) {
969
+ if (a.length !== b.length) return false;
970
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
971
+ return true;
972
+ }
934
973
  function referenceCheckAuth(entry, publicKey, networkPassphrase) {
935
974
  if (entry.credentials().switch().name !== "sorobanCredentialsAddress") {
936
975
  return fail("no address credentials");
937
976
  }
938
977
  const structMap = entry.credentials().address().signature().map();
939
978
  if (!structMap) return fail("signature is not a Secp256r1Signature map");
979
+ const inline = structMap.find((x) => x.key().sym().toString() === "public_key");
980
+ if (inline && !bytesEqual(new Uint8Array(inline.val().bytes()), publicKey)) {
981
+ return fail("unknown signer: inline public_key is not the enrolled key");
982
+ }
940
983
  const signaturePayload = authEntryChallengeBytes(entry, networkPassphrase);
941
984
  const r = verifySecp256r1StructMap(structMap, publicKey, signaturePayload);
942
985
  return { success: r.ok, challengeBound: r.challengeBound, signatureValid: r.signatureValid };
@@ -1108,8 +1151,20 @@ async function connect(options) {
1108
1151
  }
1109
1152
  }
1110
1153
  const accounts = await options.indexer.resolveByCredential(credentialId);
1111
- const contractId = accounts[0]?.contractId;
1112
- return contractId ? { contractId, credentialId } : null;
1154
+ const selected = selectAccount(accounts, options.publicKey);
1155
+ return selected ? { contractId: selected.contractId, credentialId } : null;
1156
+ }
1157
+ function bytesEqual2(a, b) {
1158
+ if (a.length !== b.length) return false;
1159
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
1160
+ return true;
1161
+ }
1162
+ function selectAccount(accounts, publicKey) {
1163
+ if (publicKey) {
1164
+ const match = accounts.find((a) => a.publicKey && bytesEqual2(a.publicKey, publicKey));
1165
+ return match ? { contractId: match.contractId } : null;
1166
+ }
1167
+ return accounts[0] ?? null;
1113
1168
  }
1114
1169
 
1115
1170
  // src/ceremonies/recover.ts
@@ -1292,7 +1347,10 @@ function defaultMatch(event, credentialId) {
1292
1347
  const cred = credRaw instanceof Uint8Array ? new TextDecoder().decode(credRaw) : String(credRaw ?? "");
1293
1348
  if (tag === "deployed" && cred === credentialId) {
1294
1349
  const addr2 = decodeNative(event.value);
1295
- if (typeof addr2 === "string" && addr2.startsWith("C")) return { contractId: addr2 };
1350
+ const pkRaw = topics.length >= 3 ? decodeNative(topics[2]) : void 0;
1351
+ const publicKey = pkRaw instanceof Uint8Array ? pkRaw : void 0;
1352
+ if (typeof addr2 === "string" && addr2.startsWith("C"))
1353
+ return { contractId: addr2, publicKey };
1296
1354
  }
1297
1355
  }
1298
1356
  const haystack = [event.value, ...topics].map((v) => v.toXDR("base64")).join("|");
@@ -1375,6 +1433,12 @@ function factoryDeployer(options) {
1375
1433
  const source = stellarSdk.Keypair.fromSecret(options.sourceSecret);
1376
1434
  return {
1377
1435
  async deploy(input) {
1436
+ if (input.publicKey.length !== 65) {
1437
+ throw new KitError(
1438
+ "CONTRACT_AUTH_FAILED",
1439
+ `factory deploy: public key must be 65-byte SEC-1 (got ${String(input.publicKey.length)})`
1440
+ );
1441
+ }
1378
1442
  const pk = stellarSdk.nativeToScVal(Buffer.from(input.publicKey), { type: "bytes" });
1379
1443
  const cred = stellarSdk.nativeToScVal(Buffer.from(new TextEncoder().encode(input.credentialId)), {
1380
1444
  type: "bytes"