@calimero-network/mero-js 22.5.1 → 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.cjs CHANGED
@@ -124,6 +124,8 @@ __export(index_exports, {
124
124
  signRequest: () => signRequest,
125
125
  signRoutingChallenge: () => signRoutingChallenge,
126
126
  signWarrant: () => signWarrant,
127
+ signerFromCryptoKey: () => signerFromCryptoKey,
128
+ signerFromSecret: () => signerFromSecret,
127
129
  transportKeyBinding: () => transportKeyBinding,
128
130
  trustSignedRelease: () => trustSignedRelease,
129
131
  trustedMeasurementsFromReleases: () => trustedMeasurementsFromReleases,
@@ -6585,6 +6587,117 @@ var LocalStorageTokenStore = class {
6585
6587
  }
6586
6588
  };
6587
6589
 
6590
+ // src/signer/signer.ts
6591
+ var PKCS8_ED25519_PREFIX2 = new Uint8Array([
6592
+ 48,
6593
+ 46,
6594
+ 2,
6595
+ 1,
6596
+ 0,
6597
+ 48,
6598
+ 5,
6599
+ 6,
6600
+ 3,
6601
+ 43,
6602
+ 101,
6603
+ 112,
6604
+ 4,
6605
+ 34,
6606
+ 4,
6607
+ 32
6608
+ ]);
6609
+ function unsupported(cause) {
6610
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
6611
+ return new Error(
6612
+ "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
6613
+ );
6614
+ }
6615
+ async function signerFromSecret(secret, label2 = "secret") {
6616
+ const seed = fromHex(secret, label2, 32);
6617
+ const pkcs8 = concat(PKCS8_ED25519_PREFIX2, seed);
6618
+ let privateKey;
6619
+ let publicKey;
6620
+ try {
6621
+ const exportable = await crypto.subtle.importKey(
6622
+ "pkcs8",
6623
+ pkcs8,
6624
+ { name: "Ed25519" },
6625
+ true,
6626
+ ["sign"]
6627
+ );
6628
+ const jwk = await crypto.subtle.exportKey("jwk", exportable);
6629
+ publicKey = hex(
6630
+ Uint8Array.from(
6631
+ atob(jwk.x.replace(/-/g, "+").replace(/_/g, "/")),
6632
+ (c) => c.charCodeAt(0)
6633
+ )
6634
+ );
6635
+ privateKey = await crypto.subtle.importKey(
6636
+ "pkcs8",
6637
+ pkcs8,
6638
+ { name: "Ed25519" },
6639
+ false,
6640
+ ["sign"]
6641
+ );
6642
+ } catch (cause) {
6643
+ throw unsupported(cause);
6644
+ }
6645
+ return fromKeyAndPublic(privateKey, publicKey);
6646
+ }
6647
+ async function signerFromCryptoKey(privateKey, publicKey) {
6648
+ if (privateKey.type !== "private") {
6649
+ throw new Error(
6650
+ `signerFromCryptoKey needs the private half of the pair, got a ${privateKey.type} key`
6651
+ );
6652
+ }
6653
+ if (!privateKey.usages.includes("sign")) {
6654
+ throw new Error(
6655
+ "this CryptoKey cannot sign: generate it with usages ['sign', 'verify']"
6656
+ );
6657
+ }
6658
+ let publicHex;
6659
+ if (typeof publicKey === "string") {
6660
+ fromHex(publicKey, "publicKey", 32);
6661
+ publicHex = publicKey.trim().toLowerCase();
6662
+ } else {
6663
+ try {
6664
+ publicHex = hex(
6665
+ new Uint8Array(await crypto.subtle.exportKey("raw", publicKey))
6666
+ );
6667
+ } catch (cause) {
6668
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
6669
+ throw new Error(
6670
+ "could not read the public half of this key pair; pass the public key as 64 hex instead" + detail
6671
+ );
6672
+ }
6673
+ }
6674
+ return fromKeyAndPublic(privateKey, publicHex);
6675
+ }
6676
+ function fromKeyAndPublic(privateKey, publicKey) {
6677
+ return {
6678
+ publicKey,
6679
+ async sign(payload) {
6680
+ try {
6681
+ return new Uint8Array(
6682
+ await crypto.subtle.sign({ name: "Ed25519" }, privateKey, payload)
6683
+ );
6684
+ } catch (cause) {
6685
+ throw unsupported(cause);
6686
+ }
6687
+ }
6688
+ };
6689
+ }
6690
+ async function resolveSigner(secret, signer, label2) {
6691
+ if (signer && secret !== void 0) {
6692
+ throw new Error(
6693
+ `pass either ${label2} or signer, not both \u2014 they may name different keys`
6694
+ );
6695
+ }
6696
+ if (signer) return signer;
6697
+ if (secret !== void 0) return signerFromSecret(secret, label2);
6698
+ throw new Error(`${label2} or signer is required`);
6699
+ }
6700
+
6588
6701
  // src/warrant/warrant.ts
6589
6702
  var SIGN_DOMAIN2 = new TextEncoder().encode("calimero.warrant.v2");
6590
6703
  var INTENT_DOMAIN = new TextEncoder().encode("calimero.warrant.intent.v1");
@@ -6605,8 +6718,8 @@ async function signWarrant(input) {
6605
6718
  );
6606
6719
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6607
6720
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6608
- const key = await importSigningKey(input.deviceSecret);
6609
- const publicKey = await derivePublicKey(input.deviceSecret);
6721
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
6722
+ const publicKey = fromHex(signer.publicKey, "signer.publicKey", 32);
6610
6723
  const method = new TextEncoder().encode(input.method);
6611
6724
  const commitment = await intentHash(input.method, input.argsJson);
6612
6725
  const nonce2 = u64le(input.nonce);
@@ -6626,9 +6739,7 @@ async function signWarrant(input) {
6626
6739
  nonce2,
6627
6740
  notAfter
6628
6741
  ]);
6629
- const signature = new Uint8Array(
6630
- await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6631
- );
6742
+ const signature = checkedSignature(await signer.sign(preimage));
6632
6743
  return hex(
6633
6744
  concat(
6634
6745
  context,
@@ -6649,6 +6760,13 @@ async function signWarrant(input) {
6649
6760
  )
6650
6761
  );
6651
6762
  }
6763
+ function checkedSignature(signature) {
6764
+ if (!(signature instanceof Uint8Array) || signature.length !== 64) {
6765
+ const got = signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature;
6766
+ throw new Error(`signer returned ${got}, expected a 64-byte Ed25519 signature`);
6767
+ }
6768
+ return signature;
6769
+ }
6652
6770
  function citedHeads(heads, label2) {
6653
6771
  const list = heads ?? [];
6654
6772
  if (list.length > MAX_CITED_HEADS) {
@@ -6698,8 +6816,8 @@ async function signCreationWarrant(input) {
6698
6816
  const name = label(input.name, "name");
6699
6817
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6700
6818
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6701
- const key = await importSigningKey(input.deviceSecret);
6702
- const deviceKey = await derivePublicKey(input.deviceSecret);
6819
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
6820
+ const deviceKey = fromHex(signer.publicKey, "signer.publicKey", 32);
6703
6821
  const initHash = await creationInitHash(input.initArgs);
6704
6822
  const preimage = await creationWarrantPreimage({
6705
6823
  group,
@@ -6716,9 +6834,7 @@ async function signCreationWarrant(input) {
6716
6834
  nonce: input.nonce,
6717
6835
  notAfter: input.notAfter
6718
6836
  });
6719
- const signature = new Uint8Array(
6720
- await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6721
- );
6837
+ const signature = checkedSignature(await signer.sign(preimage));
6722
6838
  const warrant = hex(
6723
6839
  concat(
6724
6840
  group,
@@ -6847,8 +6963,8 @@ async function signGovernanceWarrant(input) {
6847
6963
  const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6848
6964
  const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6849
6965
  const kind = kindByte(input.op.kind);
6850
- const key = await importSigningKey(input.deviceSecret);
6851
- const deviceKey = await derivePublicKey(input.deviceSecret);
6966
+ const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
6967
+ const deviceKey = fromHex(signer.publicKey, "signer.publicKey", 32);
6852
6968
  const opHash = await governanceOpHash(input.op);
6853
6969
  const preimage = await governanceWarrantPreimage({
6854
6970
  scope,
@@ -6862,9 +6978,7 @@ async function signGovernanceWarrant(input) {
6862
6978
  nonce: input.nonce,
6863
6979
  notAfter: input.notAfter
6864
6980
  });
6865
- const signature = new Uint8Array(
6866
- await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6867
- );
6981
+ const signature = checkedSignature(await signer.sign(preimage));
6868
6982
  return hex(
6869
6983
  concat(
6870
6984
  scope,
@@ -6965,6 +7079,14 @@ var RelayClient = class {
6965
7079
  this.fetchImpl = injected ? (input, init) => injected(input, init) : (input, init) => globalThis.fetch(input, init);
6966
7080
  this.timeoutMs = config.timeoutMs ?? 1e4;
6967
7081
  }
7082
+ /**
7083
+ * The author device's signer, built from `deviceSecret` when that is what was
7084
+ * configured. A {@link Signer} and never the secret, so no caller of a
7085
+ * `RelayClient` gains the ability to read key material.
7086
+ */
7087
+ async authorSigner() {
7088
+ return resolveSigner(this.config.deviceSecret, this.config.signer, "deviceSecret");
7089
+ }
6968
7090
  /**
6969
7091
  * Ask the relay what it can do in `contextId`.
6970
7092
  *
@@ -6986,6 +7108,7 @@ var RelayClient = class {
6986
7108
  */
6987
7109
  async execute(contextId, method, argsJson = {}) {
6988
7110
  const executor = this.executorAccount ?? (await this.describe(contextId)).executorAccount;
7111
+ const signer = await this.authorSigner();
6989
7112
  const nonce2 = await this.config.nonces.next();
6990
7113
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
6991
7114
  const warrant = await signWarrant({
@@ -6996,7 +7119,7 @@ var RelayClient = class {
6996
7119
  argsJson,
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",
@@ -7060,6 +7183,7 @@ var RelayClient = class {
7060
7183
  }
7061
7184
  const executor = this.checkedExecutor(described.executorAccount);
7062
7185
  const initArgs = input.initArgs ?? {};
7186
+ const signer = await this.authorSigner();
7063
7187
  const nonce2 = await this.config.nonces.next();
7064
7188
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
7065
7189
  const { warrant } = await signCreationWarrant({
@@ -7073,7 +7197,7 @@ var RelayClient = class {
7073
7197
  initArgs,
7074
7198
  nonce: nonce2,
7075
7199
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
7076
- deviceSecret: this.config.deviceSecret
7200
+ signer
7077
7201
  });
7078
7202
  const body = await this.json(
7079
7203
  "POST",
@@ -7123,6 +7247,7 @@ var RelayClient = class {
7123
7247
  );
7124
7248
  }
7125
7249
  const executor = this.checkedExecutor(described.executorAccount);
7250
+ const signer = await this.authorSigner();
7126
7251
  const nonce2 = await this.config.nonces.next();
7127
7252
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
7128
7253
  const warrant = await signGovernanceWarrant({
@@ -7132,7 +7257,7 @@ var RelayClient = class {
7132
7257
  executor,
7133
7258
  nonce: nonce2,
7134
7259
  notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
7135
- deviceSecret: this.config.deviceSecret
7260
+ signer
7136
7261
  });
7137
7262
  const body = await this.json(
7138
7263
  "POST",