@calimero-network/mero-js 22.3.0 → 22.5.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
@@ -79,6 +79,7 @@ __export(index_exports, {
79
79
  createSignedReleaseVerifier: () => createSignedReleaseVerifier,
80
80
  createTimeoutSignal: () => createTimeoutSignal,
81
81
  createUniversalHttpClient: () => createUniversalHttpClient,
82
+ creationInitHash: () => creationInitHash,
82
83
  deviceCertPayload: () => deviceCertPayload,
83
84
  discoverLocalNodes: () => discoverLocalNodes,
84
85
  encodeSignedInvitation: () => encodeSignedInvitation,
@@ -87,6 +88,10 @@ __export(index_exports, {
87
88
  fetchNodeReleaseVersion: () => fetchNodeReleaseVersion,
88
89
  generateAccountRoot: () => generateAccountRoot,
89
90
  generateSessionKey: () => generateSessionKey,
91
+ governanceOpHash: () => governanceOpHash,
92
+ groupCreatedOp: () => groupCreatedOp,
93
+ groupDeletedOp: () => groupDeletedOp,
94
+ groupReparentedOp: () => groupReparentedOp,
90
95
  hasCap: () => hasCap,
91
96
  hexEncodeUtf8: () => hexEncodeUtf8,
92
97
  intentHash: () => intentHash,
@@ -94,10 +99,16 @@ __export(index_exports, {
94
99
  jsonCodec: () => jsonCodec,
95
100
  localNodeUrl: () => localNodeUrl,
96
101
  login: () => login,
102
+ memberAddedOp: () => memberAddedOp,
103
+ memberLeftOp: () => memberLeftOp,
104
+ memberRemovedOp: () => memberRemovedOp,
105
+ memberRoleSetOp: () => memberRoleSetOp,
97
106
  mintDeviceId: () => mintDeviceId,
98
107
  nodeEndpoint: () => nodeEndpoint,
99
108
  nodeReleaseVersionFromImage: () => nodeReleaseVersionFromImage,
100
109
  parseAuthCallback: () => parseAuthCallback,
110
+ parseCreationWarrant: () => parseCreationWarrant,
111
+ parseGovernanceWarrant: () => parseGovernanceWarrant,
101
112
  probeNodeHealth: () => probeNodeHealth,
102
113
  redeemInvitation: () => redeemInvitation,
103
114
  requestBodyHash: () => requestBodyHash,
@@ -105,7 +116,9 @@ __export(index_exports, {
105
116
  shouldRetain: () => shouldRetain,
106
117
  signAccountLink: () => signAccountLink,
107
118
  signAccountLogin: () => signAccountLogin,
119
+ signCreationWarrant: () => signCreationWarrant,
108
120
  signDeviceCert: () => signDeviceCert,
121
+ signGovernanceWarrant: () => signGovernanceWarrant,
109
122
  signLoginStatement: () => signLoginStatement,
110
123
  signMemberJoinOp: () => signMemberJoinOp,
111
124
  signRequest: () => signRequest,
@@ -2306,11 +2319,11 @@ function u32le(value) {
2306
2319
  function hex(bytes) {
2307
2320
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
2308
2321
  }
2309
- function fromHex(value, label, expectedBytes) {
2322
+ function fromHex(value, label2, expectedBytes) {
2310
2323
  const clean = value.trim();
2311
2324
  if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
2312
2325
  throw new Error(
2313
- `${label} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2326
+ `${label2} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
2314
2327
  );
2315
2328
  }
2316
2329
  const bytes = new Uint8Array(expectedBytes);
@@ -2319,10 +2332,10 @@ function fromHex(value, label, expectedBytes) {
2319
2332
  }
2320
2333
  return bytes;
2321
2334
  }
2322
- function fromHexUnsized(value, label) {
2335
+ function fromHexUnsized(value, label2) {
2323
2336
  const clean = value.trim();
2324
2337
  if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
2325
- throw new Error(`${label} must be an even number of hex characters`);
2338
+ throw new Error(`${label2} must be an even number of hex characters`);
2326
2339
  }
2327
2340
  const bytes = new Uint8Array(clean.length / 2);
2328
2341
  for (let i = 0; i < bytes.length; i += 1) {
@@ -6636,14 +6649,300 @@ async function signWarrant(input) {
6636
6649
  )
6637
6650
  );
6638
6651
  }
6639
- function citedHeads(heads, label) {
6652
+ function citedHeads(heads, label2) {
6640
6653
  const list = heads ?? [];
6641
6654
  if (list.length > MAX_CITED_HEADS) {
6642
6655
  throw new Error(
6643
- `${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
6656
+ `${label2} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
6644
6657
  );
6645
6658
  }
6646
- return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));
6659
+ return list.map((head, i) => fromHex(head, `${label2}[${i}]`, 32));
6660
+ }
6661
+
6662
+ // src/warrant/creation-warrant.ts
6663
+ var SIGN_DOMAIN3 = new TextEncoder().encode("calimero.context-creation-warrant.v1");
6664
+ var INIT_DOMAIN = new TextEncoder().encode("calimero.context-creation.init.v1");
6665
+ var MAX_LABEL_BYTES = 256;
6666
+ async function creationInitHash(initArgs) {
6667
+ const bytes = new TextEncoder().encode(JSON.stringify(initArgs));
6668
+ return domainHash(INIT_DOMAIN, [bytes]);
6669
+ }
6670
+ async function creationWarrantPreimage(p) {
6671
+ return domainHash(SIGN_DOMAIN3, [
6672
+ p.group,
6673
+ p.seed,
6674
+ p.authorAccount,
6675
+ p.deviceKey,
6676
+ p.executor,
6677
+ p.applicationId,
6678
+ new Uint8Array([p.serviceName === null ? 0 : 1]),
6679
+ p.serviceName ?? new Uint8Array(0),
6680
+ new Uint8Array([p.name === null ? 0 : 1]),
6681
+ p.name ?? new Uint8Array(0),
6682
+ p.initHash,
6683
+ u64le(p.accountHeads.length),
6684
+ ...p.accountHeads,
6685
+ u64le(p.governanceFloor.length),
6686
+ ...p.governanceFloor,
6687
+ u64le(p.nonce),
6688
+ u64le(p.notAfter)
6689
+ ]);
6690
+ }
6691
+ async function signCreationWarrant(input) {
6692
+ const group = fromHex(input.group, "group", 32);
6693
+ const seed = input.seed === void 0 ? crypto.getRandomValues(new Uint8Array(32)) : fromHex(input.seed, "seed", 32);
6694
+ const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
6695
+ const executor = fromHex(input.executor, "executor", 32);
6696
+ const applicationId = fromHex(input.applicationId, "applicationId", 32);
6697
+ const serviceName = label(input.serviceName, "serviceName");
6698
+ const name = label(input.name, "name");
6699
+ const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6700
+ const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6701
+ const key = await importSigningKey(input.deviceSecret);
6702
+ const deviceKey = await derivePublicKey(input.deviceSecret);
6703
+ const initHash = await creationInitHash(input.initArgs);
6704
+ const preimage = await creationWarrantPreimage({
6705
+ group,
6706
+ seed,
6707
+ authorAccount,
6708
+ deviceKey,
6709
+ executor,
6710
+ applicationId,
6711
+ serviceName,
6712
+ name,
6713
+ initHash,
6714
+ accountHeads,
6715
+ governanceFloor,
6716
+ nonce: input.nonce,
6717
+ notAfter: input.notAfter
6718
+ });
6719
+ const signature = new Uint8Array(
6720
+ await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6721
+ );
6722
+ const warrant = hex(
6723
+ concat(
6724
+ group,
6725
+ seed,
6726
+ authorAccount,
6727
+ deviceKey,
6728
+ executor,
6729
+ applicationId,
6730
+ borshOption(serviceName),
6731
+ borshOption(name),
6732
+ initHash,
6733
+ u32le(accountHeads.length),
6734
+ ...accountHeads,
6735
+ u32le(governanceFloor.length),
6736
+ ...governanceFloor,
6737
+ u64le(input.nonce),
6738
+ u64le(input.notAfter),
6739
+ signature
6740
+ )
6741
+ );
6742
+ return { warrant, seed: hex(seed) };
6743
+ }
6744
+ function parseCreationWarrant(warrant) {
6745
+ const clean = warrant.trim();
6746
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
6747
+ throw new Error("creation warrant must be an even number of hex characters");
6748
+ }
6749
+ const bytes = new Uint8Array(clean.length / 2);
6750
+ for (let i = 0; i < bytes.length; i += 1) {
6751
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
6752
+ }
6753
+ const view = new DataView(bytes.buffer);
6754
+ let o = 0;
6755
+ const take = (n, what) => {
6756
+ if (o + n > bytes.length) {
6757
+ throw new Error(`creation warrant ends inside ${what} (${bytes.length} bytes)`);
6758
+ }
6759
+ const out = bytes.subarray(o, o + n);
6760
+ o += n;
6761
+ return out;
6762
+ };
6763
+ const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
6764
+ const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
6765
+ const option = (what) => {
6766
+ const tag2 = take(1, what)[0];
6767
+ if (tag2 === 0) return null;
6768
+ if (tag2 !== 1) throw new Error(`${what} has option tag ${tag2}, expected 0 or 1`);
6769
+ const len = u32(what);
6770
+ if (len > MAX_LABEL_BYTES) {
6771
+ throw new Error(`${what} is ${len} bytes, over the ${MAX_LABEL_BYTES} a node accepts`);
6772
+ }
6773
+ return new TextDecoder("utf-8", { fatal: true }).decode(take(len, what));
6774
+ };
6775
+ const heads = (what) => {
6776
+ const count = u32(what);
6777
+ if (count > 64) {
6778
+ throw new Error(`${what} cites ${count} heads, over the 64 a node accepts`);
6779
+ }
6780
+ return Array.from({ length: count }, () => hex(take(32, what)));
6781
+ };
6782
+ const fields = {
6783
+ group: hex(take(32, "group")),
6784
+ seed: hex(take(32, "seed")),
6785
+ authorAccount: hex(take(32, "authorAccount")),
6786
+ deviceKey: hex(take(32, "deviceKey")),
6787
+ executor: hex(take(32, "executor")),
6788
+ applicationId: hex(take(32, "applicationId")),
6789
+ serviceName: option("serviceName"),
6790
+ name: option("name"),
6791
+ initHash: hex(take(32, "initHash")),
6792
+ accountHeads: heads("accountHeads"),
6793
+ governanceFloor: heads("governanceFloor"),
6794
+ nonce: u64("nonce"),
6795
+ notAfter: u64("notAfter"),
6796
+ signature: hex(take(64, "signature"))
6797
+ };
6798
+ if (o !== bytes.length) {
6799
+ throw new Error(
6800
+ `creation warrant is ${bytes.length} bytes but its fields account for ${o}`
6801
+ );
6802
+ }
6803
+ return fields;
6804
+ }
6805
+ function label(value, what) {
6806
+ if (value === void 0) return null;
6807
+ const bytes = new TextEncoder().encode(value);
6808
+ if (bytes.length > MAX_LABEL_BYTES) {
6809
+ throw new Error(
6810
+ `${what} is ${bytes.length} UTF-8 bytes, over the ${MAX_LABEL_BYTES} a node accepts`
6811
+ );
6812
+ }
6813
+ return bytes;
6814
+ }
6815
+ function borshOption(bytes) {
6816
+ if (bytes === null) return new Uint8Array([0]);
6817
+ return concat(new Uint8Array([1]), u32le(bytes.length), bytes);
6818
+ }
6819
+
6820
+ // src/warrant/governance-warrant.ts
6821
+ var SIGN_DOMAIN4 = new TextEncoder().encode("calimero.governance-warrant.v1");
6822
+ var OP_DOMAIN = new TextEncoder().encode("calimero.governance-warrant.op.v1");
6823
+ var KIND_BYTE = { group: 0, root: 1 };
6824
+ async function governanceOpHash(op) {
6825
+ return domainHash(OP_DOMAIN, [new Uint8Array([kindByte(op.kind)]), op.bytes]);
6826
+ }
6827
+ async function governanceWarrantPreimage(p) {
6828
+ return domainHash(SIGN_DOMAIN4, [
6829
+ p.scope,
6830
+ new Uint8Array([kindByte(p.kind)]),
6831
+ p.authorAccount,
6832
+ p.deviceKey,
6833
+ p.executor,
6834
+ p.opHash,
6835
+ u64le(p.accountHeads.length),
6836
+ ...p.accountHeads,
6837
+ u64le(p.governanceFloor.length),
6838
+ ...p.governanceFloor,
6839
+ u64le(p.nonce),
6840
+ u64le(p.notAfter)
6841
+ ]);
6842
+ }
6843
+ async function signGovernanceWarrant(input) {
6844
+ const scope = fromHex(input.scope, "scope", 32);
6845
+ const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
6846
+ const executor = fromHex(input.executor, "executor", 32);
6847
+ const accountHeads = citedHeads(input.accountHeads, "accountHeads");
6848
+ const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
6849
+ const kind = kindByte(input.op.kind);
6850
+ const key = await importSigningKey(input.deviceSecret);
6851
+ const deviceKey = await derivePublicKey(input.deviceSecret);
6852
+ const opHash = await governanceOpHash(input.op);
6853
+ const preimage = await governanceWarrantPreimage({
6854
+ scope,
6855
+ kind: input.op.kind,
6856
+ authorAccount,
6857
+ deviceKey,
6858
+ executor,
6859
+ opHash,
6860
+ accountHeads,
6861
+ governanceFloor,
6862
+ nonce: input.nonce,
6863
+ notAfter: input.notAfter
6864
+ });
6865
+ const signature = new Uint8Array(
6866
+ await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
6867
+ );
6868
+ return hex(
6869
+ concat(
6870
+ scope,
6871
+ new Uint8Array([kind]),
6872
+ authorAccount,
6873
+ deviceKey,
6874
+ executor,
6875
+ opHash,
6876
+ u32le(accountHeads.length),
6877
+ ...accountHeads,
6878
+ u32le(governanceFloor.length),
6879
+ ...governanceFloor,
6880
+ u64le(input.nonce),
6881
+ u64le(input.notAfter),
6882
+ signature
6883
+ )
6884
+ );
6885
+ }
6886
+ function parseGovernanceWarrant(warrant) {
6887
+ const clean = warrant.trim();
6888
+ if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
6889
+ throw new Error("governance warrant must be an even number of hex characters");
6890
+ }
6891
+ const bytes = new Uint8Array(clean.length / 2);
6892
+ for (let i = 0; i < bytes.length; i += 1) {
6893
+ bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
6894
+ }
6895
+ const view = new DataView(bytes.buffer);
6896
+ let o = 0;
6897
+ const take = (n, what) => {
6898
+ if (o + n > bytes.length) {
6899
+ throw new Error(`governance warrant ends inside ${what} (${bytes.length} bytes)`);
6900
+ }
6901
+ const out = bytes.subarray(o, o + n);
6902
+ o += n;
6903
+ return out;
6904
+ };
6905
+ const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
6906
+ const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
6907
+ const heads = (what) => {
6908
+ const count = u32(what);
6909
+ if (count > 64) {
6910
+ throw new Error(`${what} cites ${count} heads, over the 64 a node accepts`);
6911
+ }
6912
+ return Array.from({ length: count }, () => hex(take(32, what)));
6913
+ };
6914
+ const kind = () => {
6915
+ const byte = take(1, "kind")[0];
6916
+ if (byte === 0) return "group";
6917
+ if (byte === 1) return "root";
6918
+ throw new Error(`governance warrant has kind ${byte}, expected 0 (group) or 1 (root)`);
6919
+ };
6920
+ const fields = {
6921
+ scope: hex(take(32, "scope")),
6922
+ kind: kind(),
6923
+ authorAccount: hex(take(32, "authorAccount")),
6924
+ deviceKey: hex(take(32, "deviceKey")),
6925
+ executor: hex(take(32, "executor")),
6926
+ opHash: hex(take(32, "opHash")),
6927
+ accountHeads: heads("accountHeads"),
6928
+ governanceFloor: heads("governanceFloor"),
6929
+ nonce: u64("nonce"),
6930
+ notAfter: u64("notAfter"),
6931
+ signature: hex(take(64, "signature"))
6932
+ };
6933
+ if (o !== bytes.length) {
6934
+ throw new Error(
6935
+ `governance warrant is ${bytes.length} bytes but its fields account for ${o}`
6936
+ );
6937
+ }
6938
+ return fields;
6939
+ }
6940
+ function kindByte(kind) {
6941
+ const byte = KIND_BYTE[kind];
6942
+ if (byte === void 0) {
6943
+ throw new Error(`op kind must be 'group' or 'root', got ${String(kind)}`);
6944
+ }
6945
+ return byte;
6647
6946
  }
6648
6947
 
6649
6948
  // src/relay/relay-client.ts
@@ -6706,6 +7005,156 @@ var RelayClient = class {
6706
7005
  );
6707
7006
  return { rootHash: body.data.rootHash, returns: body.data.returns ?? null };
6708
7007
  }
7008
+ /**
7009
+ * Ask the relay what it can do about creating contexts in `groupId`.
7010
+ *
7011
+ * Pass `author` (an account, hex) to also learn whether that account may
7012
+ * create here — `authorMayCreate` is present only then.
7013
+ */
7014
+ async describeCreation(groupId, opts = {}) {
7015
+ const query = opts.author ? `?author=${encodeURIComponent(opts.author)}` : "";
7016
+ const body = await this.json(
7017
+ "GET",
7018
+ `/admin-api/groups/${encodeURIComponent(groupId)}/context-intents${query}`
7019
+ );
7020
+ return body.data;
7021
+ }
7022
+ /**
7023
+ * Create a context in a group through the relay, as the author.
7024
+ *
7025
+ * Always asks {@link describeCreation} first — with the author, so both
7026
+ * standing checks happen before anything is signed. A relay without standing
7027
+ * in the group, or an author without `CAN_CREATE_CONTEXT`, is refused here as
7028
+ * an {@link IntentRefusedError} *before* a nonce is taken: the nonce source
7029
+ * is monotonic, and a warrant nobody will spend burns one for nothing.
7030
+ *
7031
+ * The creation warrant's nonce is spent in the new context's per-device
7032
+ * ledger — the same one later `execute` calls in that context draw from — so
7033
+ * it comes from the configured {@link NonceSource} like any other warrant.
7034
+ *
7035
+ * Sealing: nothing here is plaintext-specific. The request goes through the
7036
+ * injected `fetch`, so a `createAttestedSealedFetch` seals it exactly as it
7037
+ * seals `execute`. Whether the relay *accepts* it sealed is the node's call:
7038
+ * a proxy-auth relay only admits sealed requests for routes `merod` serves
7039
+ * without a credential, so `/admin-api/groups/:id/context-intents` must be on
7040
+ * that list on the core side or the request comes back
7041
+ * `403 sealed_route_unguarded`.
7042
+ */
7043
+ async createContext(input) {
7044
+ const described = await this.describeCreation(input.groupId, {
7045
+ author: this.config.authorAccount
7046
+ });
7047
+ if (!described.canCreateOnBehalf) {
7048
+ throw new IntentRefusedError(
7049
+ `the relay (${described.executorAccount}) has no standing to act for members of group ${described.groupId}; an admin must admit it as a relay or grant it CAN_AUTHOR_ON_BEHALF (checked before signing \u2014 no nonce was spent)`,
7050
+ false,
7051
+ 403
7052
+ );
7053
+ }
7054
+ if (described.authorMayCreate === false) {
7055
+ throw new IntentRefusedError(
7056
+ `the author (${this.config.authorAccount}) may not create contexts in group ${described.groupId}; it needs CAN_CREATE_CONTEXT or admin (checked before signing \u2014 no nonce was spent)`,
7057
+ false,
7058
+ 403
7059
+ );
7060
+ }
7061
+ const executor = this.checkedExecutor(described.executorAccount);
7062
+ const initArgs = input.initArgs ?? {};
7063
+ const nonce2 = await this.config.nonces.next();
7064
+ const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
7065
+ const { warrant } = await signCreationWarrant({
7066
+ group: input.groupId,
7067
+ seed: input.seed,
7068
+ authorAccount: this.config.authorAccount,
7069
+ executor,
7070
+ applicationId: input.applicationId,
7071
+ serviceName: input.serviceName,
7072
+ name: input.name,
7073
+ initArgs,
7074
+ nonce: nonce2,
7075
+ notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
7076
+ deviceSecret: this.config.deviceSecret
7077
+ });
7078
+ const body = await this.json(
7079
+ "POST",
7080
+ `/admin-api/groups/${encodeURIComponent(input.groupId)}/context-intents`,
7081
+ { warrant, authorProof: this.config.authorProof, initArgs }
7082
+ );
7083
+ return {
7084
+ contextId: body.data.contextId,
7085
+ groupId: body.data.groupId,
7086
+ memberPublicKey: body.data.memberPublicKey
7087
+ };
7088
+ }
7089
+ /**
7090
+ * Ask the relay whether it may publish governance ops for members of
7091
+ * `groupId`, and which account a warrant must name.
7092
+ */
7093
+ async describeGovernance(groupId) {
7094
+ const body = await this.json(
7095
+ "GET",
7096
+ `/admin-api/groups/${encodeURIComponent(groupId)}/governance-intents`
7097
+ );
7098
+ return body.data;
7099
+ }
7100
+ /**
7101
+ * Publish one governance op through the relay, as the author.
7102
+ *
7103
+ * The warrant commits to the op's exact bytes and is scoped to `groupId`, so
7104
+ * the relay can neither change what the op does nor spend it elsewhere. Every
7105
+ * peer applies the op as the author, so the author's own rights decide
7106
+ * (`MANAGE_MEMBERS` to add a member, `CAN_CREATE_SUBGROUP` to create a
7107
+ * subgroup, and so on); a lack of them comes back as an
7108
+ * {@link IntentRefusedError} from the relay.
7109
+ *
7110
+ * Always asks {@link describeGovernance} first and refuses *before* taking a
7111
+ * nonce when the relay has no standing, for the reason `createContext` does.
7112
+ * The nonce comes from the configured {@link NonceSource}; it is spent in a
7113
+ * per-group ledger, and a monotonic source is correct for it as for every
7114
+ * other warrant.
7115
+ */
7116
+ async govern(input) {
7117
+ const described = await this.describeGovernance(input.groupId);
7118
+ if (!described.canActOnBehalf) {
7119
+ throw new IntentRefusedError(
7120
+ `the relay (${described.executorAccount}) has no standing to act for members of group ${described.groupId}; an admin must admit it as a relay or grant it CAN_AUTHOR_ON_BEHALF (checked before signing, no nonce was spent)`,
7121
+ false,
7122
+ 403
7123
+ );
7124
+ }
7125
+ const executor = this.checkedExecutor(described.executorAccount);
7126
+ const nonce2 = await this.config.nonces.next();
7127
+ const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
7128
+ const warrant = await signGovernanceWarrant({
7129
+ scope: input.groupId,
7130
+ op: input.op,
7131
+ authorAccount: this.config.authorAccount,
7132
+ executor,
7133
+ nonce: nonce2,
7134
+ notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
7135
+ deviceSecret: this.config.deviceSecret
7136
+ });
7137
+ const body = await this.json(
7138
+ "POST",
7139
+ `/admin-api/groups/${encodeURIComponent(input.groupId)}/governance-intents`,
7140
+ { warrant, authorProof: this.config.authorProof, op: hex(input.op.bytes) }
7141
+ );
7142
+ return { groupId: body.data.groupId };
7143
+ }
7144
+ /**
7145
+ * The executor a warrant must name: the one the relay answered with, which a
7146
+ * configured account must agree with. Remembered for later calls.
7147
+ */
7148
+ checkedExecutor(answered) {
7149
+ const configured = this.config.executorAccount;
7150
+ if (configured && configured.toLowerCase() !== answered.toLowerCase()) {
7151
+ throw new Error(
7152
+ `configured executorAccount ${configured} is not the relay's account ${answered}; a warrant naming it would be unspendable`
7153
+ );
7154
+ }
7155
+ this.executorAccount = answered;
7156
+ return answered;
7157
+ }
6709
7158
  async json(method, path, body) {
6710
7159
  const headers = { Accept: "application/json" };
6711
7160
  if (body !== void 0) headers["Content-Type"] = "application/json";
@@ -7405,8 +7854,8 @@ var REGISTERS = ["mrtd", "rtmr0", "rtmr1", "rtmr2", "rtmr3"];
7405
7854
  function createQuoteVerifier(options) {
7406
7855
  const allowedMrtd = measurements(options.allowedMrtd ?? [], "allowedMrtd");
7407
7856
  const allowedImages = (options.allowedMeasurements ?? []).map((image, index) => {
7408
- const label = `allowedMeasurements[${index}]`;
7409
- return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${label}.${register}`)[0]).join();
7857
+ const label2 = `allowedMeasurements[${index}]`;
7858
+ return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${label2}.${register}`)[0]).join();
7410
7859
  });
7411
7860
  const allowedRtmr = [
7412
7861
  measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
@@ -7475,11 +7924,11 @@ function createQuoteVerifier(options) {
7475
7924
  };
7476
7925
  return Object.assign(verifier, { includeCollateral: true });
7477
7926
  }
7478
- function measurements(values, label) {
7927
+ function measurements(values, label2) {
7479
7928
  return values.map((value) => {
7480
7929
  const normalized = value.trim().toLowerCase();
7481
7930
  if (!MEASUREMENT_HEX.test(normalized)) {
7482
- throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
7931
+ throw new Error(`${label2} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
7483
7932
  }
7484
7933
  return normalized;
7485
7934
  });
@@ -7592,8 +8041,8 @@ var SIGNATURE_HASHES = {
7592
8041
  };
7593
8042
  function read(bytes, offset, limit) {
7594
8043
  if (offset + 2 > limit) throw new Error("it is truncated");
7595
- const tag = bytes[offset];
7596
- if ((tag & 31) === 31) throw new Error("it uses a tag this parser does not read");
8044
+ const tag2 = bytes[offset];
8045
+ if ((tag2 & 31) === 31) throw new Error("it uses a tag this parser does not read");
7597
8046
  let length = bytes[offset + 1];
7598
8047
  let start = offset + 2;
7599
8048
  if (length & 128) {
@@ -7605,7 +8054,7 @@ function read(bytes, offset, limit) {
7605
8054
  }
7606
8055
  const end = start + length;
7607
8056
  if (end > limit) throw new Error("it is truncated");
7608
- return { tag, bytes, offset, start, end };
8057
+ return { tag: tag2, bytes, offset, start, end };
7609
8058
  }
7610
8059
  function parseDer(bytes) {
7611
8060
  const element = read(bytes, 0, bytes.length);
@@ -7621,8 +8070,8 @@ function children(element) {
7621
8070
  }
7622
8071
  return out;
7623
8072
  }
7624
- function expectTag(element, tag, what) {
7625
- if (!element || element.tag !== tag) throw new Error(`${what} is not where it should be`);
8073
+ function expectTag(element, tag2, what) {
8074
+ if (!element || element.tag !== tag2) throw new Error(`${what} is not where it should be`);
7626
8075
  return element;
7627
8076
  }
7628
8077
  function contents(element) {
@@ -8084,11 +8533,103 @@ function splitVersion2(version) {
8084
8533
  return [[Number(match[1]), Number(match[2]), Number(match[3])], match[4]];
8085
8534
  }
8086
8535
 
8536
+ // src/warrant/governance-op.ts
8537
+ var ROLE = {
8538
+ Admin: 0,
8539
+ Member: 1,
8540
+ ReadOnly: 2
8541
+ };
8542
+ var GROUP_OP = {
8543
+ MemberAdded: 1,
8544
+ MemberRemoved: 2,
8545
+ MemberLeft: 3,
8546
+ MemberRoleSet: 4
8547
+ };
8548
+ var ROOT_OP = {
8549
+ GroupCreated: 0,
8550
+ GroupReparented: 1,
8551
+ GroupDeleted: 2
8552
+ };
8553
+ var CLEARED_HASH = new Uint8Array(32);
8554
+ var EMPTY_VEC = u32le(0);
8555
+ function role(value) {
8556
+ const byte = ROLE[value];
8557
+ if (byte === void 0) {
8558
+ throw new Error(`role must be Admin, Member or ReadOnly, got ${String(value)}`);
8559
+ }
8560
+ return new Uint8Array([byte]);
8561
+ }
8562
+ function tag(byte) {
8563
+ return new Uint8Array([byte]);
8564
+ }
8565
+ function memberAddedOp(member, memberRole) {
8566
+ return {
8567
+ kind: "group",
8568
+ bytes: concat(tag(GROUP_OP.MemberAdded), fromHex(member, "member", 32), role(memberRole))
8569
+ };
8570
+ }
8571
+ function memberRemovedOp(member) {
8572
+ return {
8573
+ kind: "group",
8574
+ bytes: concat(
8575
+ tag(GROUP_OP.MemberRemoved),
8576
+ fromHex(member, "member", 32),
8577
+ CLEARED_HASH,
8578
+ EMPTY_VEC
8579
+ )
8580
+ };
8581
+ }
8582
+ function memberLeftOp(member) {
8583
+ return {
8584
+ kind: "group",
8585
+ bytes: concat(
8586
+ tag(GROUP_OP.MemberLeft),
8587
+ fromHex(member, "member", 32),
8588
+ CLEARED_HASH,
8589
+ EMPTY_VEC
8590
+ )
8591
+ };
8592
+ }
8593
+ function memberRoleSetOp(member, memberRole) {
8594
+ return {
8595
+ kind: "group",
8596
+ bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, "member", 32), role(memberRole))
8597
+ };
8598
+ }
8599
+ function groupCreatedOp(input) {
8600
+ return {
8601
+ kind: "root",
8602
+ bytes: concat(
8603
+ tag(ROOT_OP.GroupCreated),
8604
+ fromHex(input.groupId, "groupId", 32),
8605
+ fromHex(input.parentId, "parentId", 32),
8606
+ tag(input.restricted ? 1 : 0),
8607
+ fromHex(input.admin, "admin", 32)
8608
+ )
8609
+ };
8610
+ }
8611
+ function groupReparentedOp(childGroupId, newParentId) {
8612
+ return {
8613
+ kind: "root",
8614
+ bytes: concat(
8615
+ tag(ROOT_OP.GroupReparented),
8616
+ fromHex(childGroupId, "childGroupId", 32),
8617
+ fromHex(newParentId, "newParentId", 32)
8618
+ )
8619
+ };
8620
+ }
8621
+ function groupDeletedOp(rootGroupId) {
8622
+ return {
8623
+ kind: "root",
8624
+ bytes: concat(tag(ROOT_OP.GroupDeleted), fromHex(rootGroupId, "rootGroupId", 32), EMPTY_VEC, EMPTY_VEC)
8625
+ };
8626
+ }
8627
+
8087
8628
  // src/namespace-op/namespace-op.ts
8088
- var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 12;
8629
+ var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 15;
8089
8630
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
8090
8631
  var NAMESPACE_OP_ROOT = 0;
8091
- var ROOT_OP = {
8632
+ var ROOT_OP2 = {
8092
8633
  GroupCreated: 0,
8093
8634
  GroupReparented: 1,
8094
8635
  GroupDeleted: 2,
@@ -8101,18 +8642,18 @@ var ROOT_OP = {
8101
8642
  NamespaceCreatedV2: 9,
8102
8643
  MemberJoinedViaTeeAttestation: 10
8103
8644
  };
8104
- function bytes32(value, label) {
8645
+ function bytes32(value, label2) {
8105
8646
  if (value.length !== 32) {
8106
- throw new Error(`${label} must be 32 bytes, got ${value.length}`);
8647
+ throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
8107
8648
  }
8108
8649
  return new Uint8Array(value);
8109
8650
  }
8110
- function optionalBytes32(value, label) {
8651
+ function optionalBytes32(value, label2) {
8111
8652
  if (value === null || value === void 0) {
8112
8653
  return new Uint8Array([0]);
8113
8654
  }
8114
8655
  if (value.length !== 32) {
8115
- throw new Error(`${label} must be 32 bytes, got ${value.length}`);
8656
+ throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
8116
8657
  }
8117
8658
  return concat(new Uint8Array([1]), new Uint8Array(value));
8118
8659
  }
@@ -8163,13 +8704,13 @@ async function signMemberJoinOp(input) {
8163
8704
  const expires = BigInt(input.invitation.invitation.expiration_timestamp ?? 0) !== 0n;
8164
8705
  const joinedAt = input.joinedAt ?? BigInt(Math.floor(Date.now() / 1e3));
8165
8706
  const rootOp = expires ? concat(
8166
- new Uint8Array([ROOT_OP.MemberJoinedAt]),
8707
+ new Uint8Array([ROOT_OP2.MemberJoinedAt]),
8167
8708
  fromHex(input.member, "member", 32),
8168
8709
  encodeSignedInvitation(input.invitation),
8169
8710
  u64le(joinedAt),
8170
8711
  credential
8171
8712
  ) : concat(
8172
- new Uint8Array([ROOT_OP.MemberJoined]),
8713
+ new Uint8Array([ROOT_OP2.MemberJoined]),
8173
8714
  fromHex(input.member, "member", 32),
8174
8715
  encodeSignedInvitation(input.invitation),
8175
8716
  credential
@@ -8194,7 +8735,7 @@ async function signMemberJoinOp(input) {
8194
8735
  }
8195
8736
 
8196
8737
  // src/login/login.ts
8197
- var SIGN_DOMAIN3 = new TextEncoder().encode("calimero.auth.login.v1");
8738
+ var SIGN_DOMAIN5 = new TextEncoder().encode("calimero.auth.login.v1");
8198
8739
  var AUDIENCE_TAG = {
8199
8740
  webOrigin: 0,
8200
8741
  codeSigningId: 1,
@@ -8218,12 +8759,12 @@ function audienceSigningBytes(audience) {
8218
8759
  );
8219
8760
  }
8220
8761
  function audienceWire(audience) {
8221
- const tag = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
8762
+ const tag2 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
8222
8763
  if (audience.kind === "cli") {
8223
- return tag;
8764
+ return tag2;
8224
8765
  }
8225
8766
  const body = audienceBody(audience);
8226
- return concat(tag, u32le(body.length), body);
8767
+ return concat(tag2, u32le(body.length), body);
8227
8768
  }
8228
8769
  async function signLoginStatement(input) {
8229
8770
  const node = fromHex(input.node, "node", 32);
@@ -8233,7 +8774,7 @@ async function signLoginStatement(input) {
8233
8774
  const deviceKey = await derivePublicKey(input.deviceSecret);
8234
8775
  const issuedAt = u64le(input.issuedAt);
8235
8776
  const expiresAt = u64le(input.expiresAt);
8236
- const preimage = await domainHash(SIGN_DOMAIN3, [
8777
+ const preimage = await domainHash(SIGN_DOMAIN5, [
8237
8778
  node,
8238
8779
  audienceSigningBytes(input.audience),
8239
8780
  challenge,