@soropass/core 0.1.0 → 0.2.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 (66) hide show
  1. package/dist/{checkAuth-C2AcXqFe.d.cts → checkAuth-B5ObsEsP.d.cts} +11 -7
  2. package/dist/{checkAuth-C2AcXqFe.d.ts → checkAuth-B5ObsEsP.d.ts} +11 -7
  3. package/dist/{chunk-AHFGTIBY.js → chunk-2KLSTE4E.js} +16 -4
  4. package/dist/chunk-2KLSTE4E.js.map +1 -0
  5. package/dist/{chunk-UQKNPO44.js → chunk-42ETVY6V.js} +3 -3
  6. package/dist/{chunk-UQKNPO44.js.map → chunk-42ETVY6V.js.map} +1 -1
  7. package/dist/{chunk-LM2AZXKZ.js → chunk-ODXQCLZL.js} +18 -6
  8. package/dist/chunk-ODXQCLZL.js.map +1 -0
  9. package/dist/{chunk-MMB4ZIW3.js → chunk-OHKPNVB3.js} +13 -5
  10. package/dist/chunk-OHKPNVB3.js.map +1 -0
  11. package/dist/{chunk-UV5HORMF.js → chunk-QREICNIW.js} +3 -3
  12. package/dist/{chunk-UV5HORMF.js.map → chunk-QREICNIW.js.map} +1 -1
  13. package/dist/{chunk-W75OQKHR.js → chunk-RFMHMWTY.js} +5 -3
  14. package/dist/chunk-RFMHMWTY.js.map +1 -0
  15. package/dist/{chunk-BIRF5I6I.js → chunk-XTLWH3VQ.js} +31 -7
  16. package/dist/chunk-XTLWH3VQ.js.map +1 -0
  17. package/dist/connect.cjs +14 -2
  18. package/dist/connect.cjs.map +1 -1
  19. package/dist/connect.d.cts +11 -1
  20. package/dist/connect.d.ts +11 -1
  21. package/dist/connect.js +1 -1
  22. package/dist/create.cjs +14 -4
  23. package/dist/create.cjs.map +1 -1
  24. package/dist/create.d.cts +20 -12
  25. package/dist/create.d.ts +20 -12
  26. package/dist/create.js +2 -2
  27. package/dist/index.cjs +81 -14
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +9 -9
  30. package/dist/index.d.ts +9 -9
  31. package/dist/index.js +20 -11
  32. package/dist/index.js.map +1 -1
  33. package/dist/{recover-0wbOXDjR.d.cts → recover-BOq0DQaU.d.cts} +1 -1
  34. package/dist/{recover-COnsxhYk.d.ts → recover-CL_jZ2kN.d.ts} +1 -1
  35. package/dist/recover.cjs +28 -4
  36. package/dist/recover.cjs.map +1 -1
  37. package/dist/recover.d.cts +4 -4
  38. package/dist/recover.d.ts +4 -4
  39. package/dist/recover.js +3 -3
  40. package/dist/{sign-Cfg0NM0T.d.cts → sign-O5d7Moma.d.cts} +23 -0
  41. package/dist/{sign-Cfg0NM0T.d.ts → sign-O5d7Moma.d.ts} +23 -0
  42. package/dist/sign.cjs +43 -7
  43. package/dist/sign.cjs.map +1 -1
  44. package/dist/sign.d.cts +27 -14
  45. package/dist/sign.d.ts +27 -14
  46. package/dist/sign.js +3 -3
  47. package/dist/{submit-BDKADQq0.d.cts → submit-BKogb6Y8.d.cts} +2 -2
  48. package/dist/{submit-BO0Nh49I.d.ts → submit-CKJp7XgZ.d.ts} +2 -2
  49. package/dist/testing.cjs +68 -9
  50. package/dist/testing.cjs.map +1 -1
  51. package/dist/testing.d.cts +14 -6
  52. package/dist/testing.d.ts +14 -6
  53. package/dist/testing.js +30 -7
  54. package/dist/testing.js.map +1 -1
  55. package/dist/{types-lW2h8Uxn.d.ts → types-BJsL-q2B.d.ts} +3 -3
  56. package/dist/{types-dMRWEEo0.d.cts → types-B_pVAg-T.d.cts} +3 -3
  57. package/dist/{types--WNl-Sda.d.cts → types-CJluKV7s.d.cts} +8 -0
  58. package/dist/{types--WNl-Sda.d.ts → types-CJluKV7s.d.ts} +8 -0
  59. package/dist/types.d.cts +4 -4
  60. package/dist/types.d.ts +4 -4
  61. package/package.json +1 -1
  62. package/dist/chunk-AHFGTIBY.js.map +0 -1
  63. package/dist/chunk-BIRF5I6I.js.map +0 -1
  64. package/dist/chunk-LM2AZXKZ.js.map +0 -1
  65. package/dist/chunk-MMB4ZIW3.js.map +0 -1
  66. package/dist/chunk-W75OQKHR.js.map +0 -1
package/dist/create.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { P as PasskeyCredential } from './types-lW2h8Uxn.js';
2
- export { d as assertES256, e as assertUserActivation } from './types-lW2h8Uxn.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-Cfg0NM0T.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),
@@ -780,7 +807,7 @@ async function signEntryInPlace(entry, options) {
780
807
  const challenge = authEntryChallenge(entry, options.networkPassphrase);
781
808
  const assertion = await options.sign(challenge);
782
809
  if (options.verify) preflightAssertion(assertion, challenge, options.verify);
783
- const signature = assertion.signature.length === 64 ? assertion.signature : derToCompactLowS(assertion.signature);
810
+ const signature = assertion.signature.length === 64 ? normalizeLowS(assertion.signature) : derToCompactLowS(assertion.signature);
784
811
  const normalized = {
785
812
  credentialId: assertion.credentialId,
786
813
  authenticatorData: 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) {
@@ -823,6 +857,9 @@ async function signTransaction(txXdr, options) {
823
857
  const entries = op.body().invokeHostFunctionOp().auth();
824
858
  for (const entry of entries) {
825
859
  if (entry.credentials().switch().name !== "sorobanCredentialsAddress") continue;
860
+ if (options.signerAddress !== void 0 && stellarSdk.Address.fromScAddress(entry.credentials().address().address()).toString() !== options.signerAddress) {
861
+ continue;
862
+ }
826
863
  await signEntryInPlace(entry, options);
827
864
  }
828
865
  }
@@ -923,17 +960,26 @@ function verifySecp256r1StructMap(structMap, publicKey, signaturePayload) {
923
960
  }
924
961
  const digest = sha256.sha256(concatBytes(authenticatorData, sha256.sha256(clientDataJSON)));
925
962
  const signatureValid = nist.p256.verify(signature, digest, publicKey, {
926
- lowS: false,
963
+ lowS: true,
927
964
  format: "compact"
928
965
  });
929
966
  return { ok: challengeBound && signatureValid, challengeBound, signatureValid };
930
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
+ }
931
973
  function referenceCheckAuth(entry, publicKey, networkPassphrase) {
932
974
  if (entry.credentials().switch().name !== "sorobanCredentialsAddress") {
933
975
  return fail("no address credentials");
934
976
  }
935
977
  const structMap = entry.credentials().address().signature().map();
936
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
+ }
937
983
  const signaturePayload = authEntryChallengeBytes(entry, networkPassphrase);
938
984
  const r = verifySecp256r1StructMap(structMap, publicKey, signaturePayload);
939
985
  return { success: r.ok, challengeBound: r.challengeBound, signatureValid: r.signatureValid };
@@ -1105,8 +1151,20 @@ async function connect(options) {
1105
1151
  }
1106
1152
  }
1107
1153
  const accounts = await options.indexer.resolveByCredential(credentialId);
1108
- const contractId = accounts[0]?.contractId;
1109
- 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;
1110
1168
  }
1111
1169
 
1112
1170
  // src/ceremonies/recover.ts
@@ -1289,7 +1347,10 @@ function defaultMatch(event, credentialId) {
1289
1347
  const cred = credRaw instanceof Uint8Array ? new TextDecoder().decode(credRaw) : String(credRaw ?? "");
1290
1348
  if (tag === "deployed" && cred === credentialId) {
1291
1349
  const addr2 = decodeNative(event.value);
1292
- 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 };
1293
1354
  }
1294
1355
  }
1295
1356
  const haystack = [event.value, ...topics].map((v) => v.toXDR("base64")).join("|");
@@ -1372,6 +1433,12 @@ function factoryDeployer(options) {
1372
1433
  const source = stellarSdk.Keypair.fromSecret(options.sourceSecret);
1373
1434
  return {
1374
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
+ }
1375
1442
  const pk = stellarSdk.nativeToScVal(Buffer.from(input.publicKey), { type: "bytes" });
1376
1443
  const cred = stellarSdk.nativeToScVal(Buffer.from(new TextEncoder().encode(input.credentialId)), {
1377
1444
  type: "bytes"