@calimero-network/mero-js 22.4.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
@@ -88,6 +88,10 @@ __export(index_exports, {
88
88
  fetchNodeReleaseVersion: () => fetchNodeReleaseVersion,
89
89
  generateAccountRoot: () => generateAccountRoot,
90
90
  generateSessionKey: () => generateSessionKey,
91
+ governanceOpHash: () => governanceOpHash,
92
+ groupCreatedOp: () => groupCreatedOp,
93
+ groupDeletedOp: () => groupDeletedOp,
94
+ groupReparentedOp: () => groupReparentedOp,
91
95
  hasCap: () => hasCap,
92
96
  hexEncodeUtf8: () => hexEncodeUtf8,
93
97
  intentHash: () => intentHash,
@@ -95,11 +99,16 @@ __export(index_exports, {
95
99
  jsonCodec: () => jsonCodec,
96
100
  localNodeUrl: () => localNodeUrl,
97
101
  login: () => login,
102
+ memberAddedOp: () => memberAddedOp,
103
+ memberLeftOp: () => memberLeftOp,
104
+ memberRemovedOp: () => memberRemovedOp,
105
+ memberRoleSetOp: () => memberRoleSetOp,
98
106
  mintDeviceId: () => mintDeviceId,
99
107
  nodeEndpoint: () => nodeEndpoint,
100
108
  nodeReleaseVersionFromImage: () => nodeReleaseVersionFromImage,
101
109
  parseAuthCallback: () => parseAuthCallback,
102
110
  parseCreationWarrant: () => parseCreationWarrant,
111
+ parseGovernanceWarrant: () => parseGovernanceWarrant,
103
112
  probeNodeHealth: () => probeNodeHealth,
104
113
  redeemInvitation: () => redeemInvitation,
105
114
  requestBodyHash: () => requestBodyHash,
@@ -109,6 +118,7 @@ __export(index_exports, {
109
118
  signAccountLogin: () => signAccountLogin,
110
119
  signCreationWarrant: () => signCreationWarrant,
111
120
  signDeviceCert: () => signDeviceCert,
121
+ signGovernanceWarrant: () => signGovernanceWarrant,
112
122
  signLoginStatement: () => signLoginStatement,
113
123
  signMemberJoinOp: () => signMemberJoinOp,
114
124
  signRequest: () => signRequest,
@@ -6753,9 +6763,9 @@ function parseCreationWarrant(warrant) {
6753
6763
  const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
6754
6764
  const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
6755
6765
  const option = (what) => {
6756
- const tag = take(1, what)[0];
6757
- if (tag === 0) return null;
6758
- if (tag !== 1) throw new Error(`${what} has option tag ${tag}, expected 0 or 1`);
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`);
6759
6769
  const len = u32(what);
6760
6770
  if (len > MAX_LABEL_BYTES) {
6761
6771
  throw new Error(`${what} is ${len} bytes, over the ${MAX_LABEL_BYTES} a node accepts`);
@@ -6807,6 +6817,134 @@ function borshOption(bytes) {
6807
6817
  return concat(new Uint8Array([1]), u32le(bytes.length), bytes);
6808
6818
  }
6809
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;
6946
+ }
6947
+
6810
6948
  // src/relay/relay-client.ts
6811
6949
  var DEFAULT_TTL_SECONDS = 300;
6812
6950
  var IntentRefusedError = class extends Error {
@@ -6920,14 +7058,7 @@ var RelayClient = class {
6920
7058
  403
6921
7059
  );
6922
7060
  }
6923
- const configured = this.config.executorAccount;
6924
- if (configured && configured.toLowerCase() !== described.executorAccount.toLowerCase()) {
6925
- throw new Error(
6926
- `configured executorAccount ${configured} is not the relay's account ${described.executorAccount}; a warrant naming it would be unspendable`
6927
- );
6928
- }
6929
- const executor = described.executorAccount;
6930
- this.executorAccount = executor;
7061
+ const executor = this.checkedExecutor(described.executorAccount);
6931
7062
  const initArgs = input.initArgs ?? {};
6932
7063
  const nonce2 = await this.config.nonces.next();
6933
7064
  const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
@@ -6955,6 +7086,75 @@ var RelayClient = class {
6955
7086
  memberPublicKey: body.data.memberPublicKey
6956
7087
  };
6957
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
+ }
6958
7158
  async json(method, path, body) {
6959
7159
  const headers = { Accept: "application/json" };
6960
7160
  if (body !== void 0) headers["Content-Type"] = "application/json";
@@ -7841,8 +8041,8 @@ var SIGNATURE_HASHES = {
7841
8041
  };
7842
8042
  function read(bytes, offset, limit) {
7843
8043
  if (offset + 2 > limit) throw new Error("it is truncated");
7844
- const tag = bytes[offset];
7845
- 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");
7846
8046
  let length = bytes[offset + 1];
7847
8047
  let start = offset + 2;
7848
8048
  if (length & 128) {
@@ -7854,7 +8054,7 @@ function read(bytes, offset, limit) {
7854
8054
  }
7855
8055
  const end = start + length;
7856
8056
  if (end > limit) throw new Error("it is truncated");
7857
- return { tag, bytes, offset, start, end };
8057
+ return { tag: tag2, bytes, offset, start, end };
7858
8058
  }
7859
8059
  function parseDer(bytes) {
7860
8060
  const element = read(bytes, 0, bytes.length);
@@ -7870,8 +8070,8 @@ function children(element) {
7870
8070
  }
7871
8071
  return out;
7872
8072
  }
7873
- function expectTag(element, tag, what) {
7874
- 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`);
7875
8075
  return element;
7876
8076
  }
7877
8077
  function contents(element) {
@@ -8333,11 +8533,103 @@ function splitVersion2(version) {
8333
8533
  return [[Number(match[1]), Number(match[2]), Number(match[3])], match[4]];
8334
8534
  }
8335
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
+
8336
8628
  // src/namespace-op/namespace-op.ts
8337
- var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 12;
8629
+ var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 15;
8338
8630
  var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
8339
8631
  var NAMESPACE_OP_ROOT = 0;
8340
- var ROOT_OP = {
8632
+ var ROOT_OP2 = {
8341
8633
  GroupCreated: 0,
8342
8634
  GroupReparented: 1,
8343
8635
  GroupDeleted: 2,
@@ -8412,13 +8704,13 @@ async function signMemberJoinOp(input) {
8412
8704
  const expires = BigInt(input.invitation.invitation.expiration_timestamp ?? 0) !== 0n;
8413
8705
  const joinedAt = input.joinedAt ?? BigInt(Math.floor(Date.now() / 1e3));
8414
8706
  const rootOp = expires ? concat(
8415
- new Uint8Array([ROOT_OP.MemberJoinedAt]),
8707
+ new Uint8Array([ROOT_OP2.MemberJoinedAt]),
8416
8708
  fromHex(input.member, "member", 32),
8417
8709
  encodeSignedInvitation(input.invitation),
8418
8710
  u64le(joinedAt),
8419
8711
  credential
8420
8712
  ) : concat(
8421
- new Uint8Array([ROOT_OP.MemberJoined]),
8713
+ new Uint8Array([ROOT_OP2.MemberJoined]),
8422
8714
  fromHex(input.member, "member", 32),
8423
8715
  encodeSignedInvitation(input.invitation),
8424
8716
  credential
@@ -8443,7 +8735,7 @@ async function signMemberJoinOp(input) {
8443
8735
  }
8444
8736
 
8445
8737
  // src/login/login.ts
8446
- var SIGN_DOMAIN4 = new TextEncoder().encode("calimero.auth.login.v1");
8738
+ var SIGN_DOMAIN5 = new TextEncoder().encode("calimero.auth.login.v1");
8447
8739
  var AUDIENCE_TAG = {
8448
8740
  webOrigin: 0,
8449
8741
  codeSigningId: 1,
@@ -8467,12 +8759,12 @@ function audienceSigningBytes(audience) {
8467
8759
  );
8468
8760
  }
8469
8761
  function audienceWire(audience) {
8470
- const tag = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
8762
+ const tag2 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
8471
8763
  if (audience.kind === "cli") {
8472
- return tag;
8764
+ return tag2;
8473
8765
  }
8474
8766
  const body = audienceBody(audience);
8475
- return concat(tag, u32le(body.length), body);
8767
+ return concat(tag2, u32le(body.length), body);
8476
8768
  }
8477
8769
  async function signLoginStatement(input) {
8478
8770
  const node = fromHex(input.node, "node", 32);
@@ -8482,7 +8774,7 @@ async function signLoginStatement(input) {
8482
8774
  const deviceKey = await derivePublicKey(input.deviceSecret);
8483
8775
  const issuedAt = u64le(input.issuedAt);
8484
8776
  const expiresAt = u64le(input.expiresAt);
8485
- const preimage = await domainHash(SIGN_DOMAIN4, [
8777
+ const preimage = await domainHash(SIGN_DOMAIN5, [
8486
8778
  node,
8487
8779
  audienceSigningBytes(input.audience),
8488
8780
  challenge,