@calimero-network/mero-js 22.5.0 → 22.6.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.
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export * from "./cloud/index.js";
19
19
  export * from "./sealed/index.js";
20
20
  export * from "./relay/index.js";
21
21
  export { signWarrant, intentHash, signCreationWarrant, parseCreationWarrant, creationInitHash, signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, } from "./warrant/index.js";
22
+ export { signerFromSecret, signerFromCryptoKey } from "./signer/index.js";
23
+ export type { Signer } from "./signer/index.js";
22
24
  export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, signAccountLink, signAccountLogin, } from "./account/index.js";
23
25
  export type { AccountRoot, RecoverableAccountRoot, AccountLinkInput, AccountLoginInput, } from "./account/index.js";
24
26
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, } from "./device-cert/index.js";
package/dist/index.js CHANGED
@@ -28,6 +28,9 @@ export * from "./sealed/index.js";
28
28
  export * from "./relay/index.js";
29
29
  // Warrant signing — mint the author's consent for a relay to run one intent
30
30
  export { signWarrant, intentHash, signCreationWarrant, parseCreationWarrant, creationInitHash, signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, } from "./warrant/index.js";
31
+ // Who signs — a key's capability, separated from its material, so a
32
+ // non-extractable key can be used wherever a hex secret could.
33
+ export { signerFromSecret, signerFromCryptoKey } from "./signer/index.js";
31
34
  // Account roots — mint one with a recovery phrase, or prove you hold one
32
35
  export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, signAccountLink, signAccountLogin, } from "./account/index.js";
33
36
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, } from "./device-cert/index.js";
package/dist/index.mjs CHANGED
@@ -6449,6 +6449,117 @@ var LocalStorageTokenStore = class {
6449
6449
  }
6450
6450
  };
6451
6451
 
6452
+ // src/signer/signer.ts
6453
+ var PKCS8_ED25519_PREFIX2 = new Uint8Array([
6454
+ 48,
6455
+ 46,
6456
+ 2,
6457
+ 1,
6458
+ 0,
6459
+ 48,
6460
+ 5,
6461
+ 6,
6462
+ 3,
6463
+ 43,
6464
+ 101,
6465
+ 112,
6466
+ 4,
6467
+ 34,
6468
+ 4,
6469
+ 32
6470
+ ]);
6471
+ function unsupported(cause) {
6472
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
6473
+ return new Error(
6474
+ "this runtime has no WebCrypto Ed25519, so nothing can be signed here. Node 18.4+, Safari 17+, Firefox 129+ and Chrome 137+ have it" + detail
6475
+ );
6476
+ }
6477
+ async function signerFromSecret(secret, label2 = "secret") {
6478
+ const seed = fromHex(secret, label2, 32);
6479
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX2, seed);
6480
+ let privateKey;
6481
+ let publicKey;
6482
+ try {
6483
+ const exportable = await crypto.subtle.importKey(
6484
+ "pkcs8",
6485
+ pkcs8,
6486
+ { name: "Ed25519" },
6487
+ true,
6488
+ ["sign"]
6489
+ );
6490
+ const jwk = await crypto.subtle.exportKey("jwk", exportable);
6491
+ publicKey = hex(
6492
+ Uint8Array.from(
6493
+ atob(jwk.x.replace(/-/g, "+").replace(/_/g, "/")),
6494
+ (c) => c.charCodeAt(0)
6495
+ )
6496
+ );
6497
+ privateKey = await crypto.subtle.importKey(
6498
+ "pkcs8",
6499
+ pkcs8,
6500
+ { name: "Ed25519" },
6501
+ false,
6502
+ ["sign"]
6503
+ );
6504
+ } catch (cause) {
6505
+ throw unsupported(cause);
6506
+ }
6507
+ return fromKeyAndPublic(privateKey, publicKey);
6508
+ }
6509
+ async function signerFromCryptoKey(privateKey, publicKey) {
6510
+ if (privateKey.type !== "private") {
6511
+ throw new Error(
6512
+ `signerFromCryptoKey needs the private half of the pair, got a ${privateKey.type} key`
6513
+ );
6514
+ }
6515
+ if (!privateKey.usages.includes("sign")) {
6516
+ throw new Error(
6517
+ "this CryptoKey cannot sign: generate it with usages ['sign', 'verify']"
6518
+ );
6519
+ }
6520
+ let publicHex;
6521
+ if (typeof publicKey === "string") {
6522
+ fromHex(publicKey, "publicKey", 32);
6523
+ publicHex = publicKey.trim().toLowerCase();
6524
+ } else {
6525
+ try {
6526
+ publicHex = hex(
6527
+ new Uint8Array(await crypto.subtle.exportKey("raw", publicKey))
6528
+ );
6529
+ } catch (cause) {
6530
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
6531
+ throw new Error(
6532
+ "could not read the public half of this key pair; pass the public key as 64 hex instead" + detail
6533
+ );
6534
+ }
6535
+ }
6536
+ return fromKeyAndPublic(privateKey, publicHex);
6537
+ }
6538
+ function fromKeyAndPublic(privateKey, publicKey) {
6539
+ return {
6540
+ publicKey,
6541
+ async sign(payload) {
6542
+ try {
6543
+ return new Uint8Array(
6544
+ await crypto.subtle.sign({ name: "Ed25519" }, privateKey, payload)
6545
+ );
6546
+ } catch (cause) {
6547
+ throw unsupported(cause);
6548
+ }
6549
+ }
6550
+ };
6551
+ }
6552
+ async function resolveSigner(secret, signer, label2) {
6553
+ if (signer && secret !== void 0) {
6554
+ throw new Error(
6555
+ `pass either ${label2} or signer, not both \u2014 they may name different keys`
6556
+ );
6557
+ }
6558
+ if (signer) return signer;
6559
+ if (secret !== void 0) return signerFromSecret(secret, label2);
6560
+ throw new Error(`${label2} or signer is required`);
6561
+ }
6562
+
6452
6563
  // src/warrant/warrant.ts
6453
6564
  var SIGN_DOMAIN2 = new TextEncoder().encode("calimero.warrant.v2");
6454
6565
  var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
@@ -6469,8 +6580,8 @@ async function signWarrant(input) {
6469
6580
  );
6470
6581
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6471
6582
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6472
- const key = await importSigningKey(input.deviceSecret);
6473
- const publicKey = await derivePublicKey(input.deviceSecret);
6583
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
6584
+ const publicKey = fromHex(signer.publicKey, "signer.publicKey", 32);
6474
6585
  const method = new TextEncoder().encode(input.method);
6475
6586
  const commitment = await intentHash(input.method, input.argsJson);
6476
6587
  const nonce2 = u64le(input.nonce);
@@ -6490,9 +6601,7 @@ async function signWarrant(input) {
6490
6601
  nonce2,
6491
6602
  notAfter
6492
6603
  ]);
6493
- const signature = new Uint8Array(
6494
- await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6495
- );
6604
+ const signature = checkedSignature(await signer.sign(preimage));
6496
6605
  return hex(
6497
6606
  concat(
6498
6607
  context,
@@ -6513,6 +6622,13 @@ async function signWarrant(input) {
6513
6622
  )
6514
6623
  );
6515
6624
  }
6625
+ function checkedSignature(signature) {
6626
+ if (!(signature instanceof Uint8Array) || signature.length !== 64) {
6627
+ const got = signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature;
6628
+ throw new Error(`signer returned ${got}, expected a 64-byte Ed25519 signature`);
6629
+ }
6630
+ return signature;
6631
+ }
6516
6632
  function citedHeads(heads, label2) {
6517
6633
  const list = heads ?? [];
6518
6634
  if (list.length > MAX_CITED_HEADS) {
@@ -6562,8 +6678,8 @@ async function signCreationWarrant(input) {
6562
6678
  const name = label(input.name, "name");
6563
6679
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6564
6680
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6565
- const key = await importSigningKey(input.deviceSecret);
6566
- const deviceKey = await derivePublicKey(input.deviceSecret);
6681
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
6682
+ const deviceKey = fromHex(signer.publicKey, "signer.publicKey", 32);
6567
6683
  const initHash = await creationInitHash(input.initArgs);
6568
6684
  const preimage = await creationWarrantPreimage({
6569
6685
  group,
@@ -6580,9 +6696,7 @@ async function signCreationWarrant(input) {
6580
6696
  nonce: input.nonce,
6581
6697
  notAfter: input.notAfter
6582
6698
  });
6583
- const signature = new Uint8Array(
6584
- await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6585
- );
6699
+ const signature = checkedSignature(await signer.sign(preimage));
6586
6700
  const warrant = hex(
6587
6701
  concat(
6588
6702
  group,
@@ -6711,8 +6825,8 @@ async function signGovernanceWarrant(input) {
6711
6825
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6712
6826
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6713
6827
  const kind = kindByte(input.op.kind);
6714
- const key = await importSigningKey(input.deviceSecret);
6715
- const deviceKey = await derivePublicKey(input.deviceSecret);
6828
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
6829
+ const deviceKey = fromHex(signer.publicKey, "signer.publicKey", 32);
6716
6830
  const opHash = await governanceOpHash(input.op);
6717
6831
  const preimage = await governanceWarrantPreimage({
6718
6832
  scope,
@@ -6726,9 +6840,7 @@ async function signGovernanceWarrant(input) {
6726
6840
  nonce: input.nonce,
6727
6841
  notAfter: input.notAfter
6728
6842
  });
6729
- const signature = new Uint8Array(
6730
- await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6731
- );
6843
+ const signature = checkedSignature(await signer.sign(preimage));
6732
6844
  return hex(
6733
6845
  concat(
6734
6846
  scope,
@@ -6829,6 +6941,14 @@ var RelayClient = class {
6829
6941
  this.fetchImpl = injected ? (input, init) => injected(input, init) : (input, init) => globalThis.fetch(input, init);
6830
6942
  this.timeoutMs = config.timeoutMs ?? 1e4;
6831
6943
  }
6944
+ /**
6945
+ * The author device's signer, built from `deviceSecret` when that is what was
6946
+ * configured. A {@link Signer} and never the secret, so no caller of a
6947
+ * `RelayClient` gains the ability to read key material.
6948
+ */
6949
+ async authorSigner() {
6950
+ return resolveSigner(this.config.deviceSecret, this.config.signer, "deviceSecret");
6951
+ }
6832
6952
  /**
6833
6953
  * Ask the relay what it can do in `contextId`.
6834
6954
  *
@@ -6850,6 +6970,7 @@ var RelayClient = class {
6850
6970
  */
6851
6971
  async execute(contextId, method, argsJson = {}) {
6852
6972
  const executor = this.executorAccount ?? (await this.describe(contextId)).executorAccount;
6973
+ const signer = await this.authorSigner();
6853
6974
  const nonce2 = await this.config.nonces.next();
6854
6975
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
6855
6976
  const warrant = await signWarrant({
@@ -6860,7 +6981,7 @@ var RelayClient = class {
6860
6981
  argsJson,
6861
6982
  nonce: nonce2,
6862
6983
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
6863
- deviceSecret: this.config.deviceSecret
6984
+ signer
6864
6985
  });
6865
6986
  const body = await this.json(
6866
6987
  "POST",
@@ -6924,6 +7045,7 @@ var RelayClient = class {
6924
7045
  }
6925
7046
  const executor = this.checkedExecutor(described.executorAccount);
6926
7047
  const initArgs = input.initArgs ?? {};
7048
+ const signer = await this.authorSigner();
6927
7049
  const nonce2 = await this.config.nonces.next();
6928
7050
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
6929
7051
  const { warrant } = await signCreationWarrant({
@@ -6937,7 +7059,7 @@ var RelayClient = class {
6937
7059
  initArgs,
6938
7060
  nonce: nonce2,
6939
7061
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
6940
- deviceSecret: this.config.deviceSecret
7062
+ signer
6941
7063
  });
6942
7064
  const body = await this.json(
6943
7065
  "POST",
@@ -6987,6 +7109,7 @@ var RelayClient = class {
6987
7109
  );
6988
7110
  }
6989
7111
  const executor = this.checkedExecutor(described.executorAccount);
7112
+ const signer = await this.authorSigner();
6990
7113
  const nonce2 = await this.config.nonces.next();
6991
7114
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
6992
7115
  const warrant = await signGovernanceWarrant({
@@ -6996,7 +7119,7 @@ var RelayClient = class {
6996
7119
  executor,
6997
7120
  nonce: nonce2,
6998
7121
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
6999
- deviceSecret: this.config.deviceSecret
7122
+ signer
7000
7123
  });
7001
7124
  const body = await this.json(
7002
7125
  "POST",
@@ -8490,7 +8613,7 @@ function groupDeletedOp(rootGroupId) {
8490
8613
  }
8491
8614
 
8492
8615
  // src/namespace-op/namespace-op.ts
8493
- var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 15;
8616
+ var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 16;
8494
8617
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
8495
8618
  var NAMESPACE_OP_ROOT = 0;
8496
8619
  var ROOT_OP2 = {
@@ -8918,6 +9041,8 @@ export {
8918
9041
  signRequest,
8919
9042
  signRoutingChallenge,
8920
9043
  signWarrant,
9044
+ signerFromCryptoKey,
9045
+ signerFromSecret,
8921
9046
  transportKeyBinding,
8922
9047
  trustSignedRelease,
8923
9048
  trustedMeasurementsFromReleases,