@calimero-network/mero-js 22.8.0 → 23.0.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/admin-api/admin-client.d.ts +38 -2
- package/dist/admin-api/admin-client.js +56 -2
- package/dist/admin-api/admin-types.d.ts +94 -0
- package/dist/device-cert/device-cert.d.ts +11 -0
- package/dist/device-cert/device-cert.js +1 -2
- package/dist/device-cert/device-scope.d.ts +58 -0
- package/dist/device-cert/device-scope.js +76 -0
- package/dist/device-cert/index.d.ts +2 -0
- package/dist/device-cert/index.js +1 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +435 -34
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +8 -2
- package/dist/index.js +7 -1
- package/dist/index.mjs +435 -34
- package/dist/index.mjs.map +4 -4
- package/dist/invitation/index.d.ts +2 -0
- package/dist/invitation/index.js +2 -0
- package/dist/invitation/invitation.d.ts +106 -0
- package/dist/invitation/invitation.js +104 -0
- package/dist/namespace-op/namespace-op.d.ts +19 -1
- package/dist/namespace-op/namespace-op.js +23 -15
- package/dist/owner-op/index.d.ts +2 -0
- package/dist/owner-op/index.js +3 -0
- package/dist/owner-op/owner-op.d.ts +119 -0
- package/dist/owner-op/owner-op.js +214 -0
- package/dist/warrant/governance-op.d.ts +31 -1
- package/dist/warrant/governance-op.js +30 -1
- package/dist/warrant/index.d.ts +2 -2
- package/dist/warrant/index.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,12 +30,15 @@ __export(index_exports, {
|
|
|
30
30
|
EphemeralClient: () => EphemeralClient,
|
|
31
31
|
HANDSHAKE_PATH: () => HANDSHAKE_PATH,
|
|
32
32
|
HTTPError: () => HTTPError,
|
|
33
|
+
INVITED_ROLE: () => INVITED_ROLE,
|
|
33
34
|
IntentRefusedError: () => IntentRefusedError,
|
|
34
35
|
LocalStorageTokenStore: () => LocalStorageTokenStore,
|
|
36
|
+
MAX_INVITATION_VALIDITY_SECS: () => MAX_INVITATION_VALIDITY_SECS,
|
|
35
37
|
MemoryTokenStore: () => MemoryTokenStore,
|
|
36
38
|
MeroClient: () => MeroClient,
|
|
37
39
|
MeroJs: () => MeroJs,
|
|
38
40
|
NODE_RELEASE_SIGNER: () => NODE_RELEASE_SIGNER,
|
|
41
|
+
OWNER_OP_KIND: () => OWNER_OP_KIND,
|
|
39
42
|
REQUEST_PROOF_HEADER: () => REQUEST_PROOF_HEADER,
|
|
40
43
|
RelayClient: () => RelayClient,
|
|
41
44
|
RelayObserver: () => RelayObserver,
|
|
@@ -57,6 +60,7 @@ __export(index_exports, {
|
|
|
57
60
|
accountRootFromPhrase: () => accountRootFromPhrase,
|
|
58
61
|
accountRootFromSecret: () => accountRootFromSecret,
|
|
59
62
|
accountRootSignerFromPhrase: () => accountRootSignerFromPhrase,
|
|
63
|
+
adminChangedOp: () => adminChangedOp,
|
|
60
64
|
admitterAddrsFromNetworkStatus: () => admitterAddrsFromNetworkStatus,
|
|
61
65
|
attestKeyBinding: () => attestKeyBinding,
|
|
62
66
|
attestRelayNodeKey: () => attestRelayNodeKey,
|
|
@@ -104,12 +108,16 @@ __export(index_exports, {
|
|
|
104
108
|
createSignedReleaseVerifier: () => createSignedReleaseVerifier,
|
|
105
109
|
createTimeoutSignal: () => createTimeoutSignal,
|
|
106
110
|
createUniversalHttpClient: () => createUniversalHttpClient,
|
|
111
|
+
createdSubgroupId: () => createdSubgroupId,
|
|
107
112
|
creationInitHash: () => creationInitHash,
|
|
113
|
+
defaultAdmitters: () => defaultAdmitters,
|
|
108
114
|
defaultAudience: () => defaultAudience,
|
|
109
115
|
defaultCapabilitiesSetOp: () => defaultCapabilitiesSetOp,
|
|
110
116
|
deviceCertPayload: () => deviceCertPayload,
|
|
111
117
|
deviceEnrolmentUrl: () => deviceEnrolmentUrl,
|
|
118
|
+
deviceScopePayload: () => deviceScopePayload,
|
|
112
119
|
discoverLocalNodes: () => discoverLocalNodes,
|
|
120
|
+
encodeGroupInvitation: () => encodeGroupInvitation,
|
|
113
121
|
encodeSignedInvitation: () => encodeSignedInvitation,
|
|
114
122
|
fetchAttestedTransportKey: () => fetchAttestedTransportKey,
|
|
115
123
|
fetchNodeRelease: () => fetchNodeRelease,
|
|
@@ -119,7 +127,9 @@ __export(index_exports, {
|
|
|
119
127
|
generateSessionKey: () => generateSessionKey,
|
|
120
128
|
governanceOpHash: () => governanceOpHash,
|
|
121
129
|
groupCreatedOp: () => groupCreatedOp,
|
|
130
|
+
groupDeleteOp: () => groupDeleteOp,
|
|
122
131
|
groupDeletedOp: () => groupDeletedOp,
|
|
132
|
+
groupInvitationHash: () => groupInvitationHash,
|
|
123
133
|
groupReparentedOp: () => groupReparentedOp,
|
|
124
134
|
hasCap: () => hasCap,
|
|
125
135
|
hexEncodeUtf8: () => hexEncodeUtf8,
|
|
@@ -136,6 +146,8 @@ __export(index_exports, {
|
|
|
136
146
|
namespaceCreatedOp: () => namespaceCreatedOp,
|
|
137
147
|
nodeEndpoint: () => nodeEndpoint,
|
|
138
148
|
nodeReleaseVersionFromImage: () => nodeReleaseVersionFromImage,
|
|
149
|
+
ownerOpDigest: () => ownerOpDigest,
|
|
150
|
+
ownerOpSigningPayload: () => ownerOpSigningPayload,
|
|
139
151
|
parseAuthCallback: () => parseAuthCallback,
|
|
140
152
|
parseCreationWarrant: () => parseCreationWarrant,
|
|
141
153
|
parseDeviceCredential: () => parseDeviceCredential,
|
|
@@ -149,15 +161,19 @@ __export(index_exports, {
|
|
|
149
161
|
requestBodyHash: () => requestBodyHash,
|
|
150
162
|
resolveRoot: () => resolveRoot,
|
|
151
163
|
resolveRootPair: () => resolveRootPair,
|
|
164
|
+
rootGuardedOpBytes: () => rootGuardedOpBytes,
|
|
152
165
|
routingProofHeaders: () => routingProofHeaders,
|
|
153
166
|
shouldRetain: () => shouldRetain,
|
|
154
167
|
signAccountLink: () => signAccountLink,
|
|
155
168
|
signAccountLogin: () => signAccountLogin,
|
|
156
169
|
signCreationWarrant: () => signCreationWarrant,
|
|
157
170
|
signDeviceCert: () => signDeviceCert,
|
|
171
|
+
signDeviceScope: () => signDeviceScope,
|
|
158
172
|
signGovernanceWarrant: () => signGovernanceWarrant,
|
|
173
|
+
signGroupInvitation: () => signGroupInvitation,
|
|
159
174
|
signLoginStatement: () => signLoginStatement,
|
|
160
175
|
signMemberJoinOp: () => signMemberJoinOp,
|
|
176
|
+
signOwnerOpProof: () => signOwnerOpProof,
|
|
161
177
|
signRequest: () => signRequest,
|
|
162
178
|
signRequestProof: () => signRequestProof,
|
|
163
179
|
signRoutingChallenge: () => signRoutingChallenge,
|
|
@@ -165,6 +181,11 @@ __export(index_exports, {
|
|
|
165
181
|
signedPath: () => signedPath,
|
|
166
182
|
signerFromCryptoKey: () => signerFromCryptoKey,
|
|
167
183
|
signerFromSecret: () => signerFromSecret,
|
|
184
|
+
subgroupCreation: () => subgroupCreation,
|
|
185
|
+
teeAdmissionPolicyOp: () => teeAdmissionPolicyOp,
|
|
186
|
+
teeAuthoringPolicyOp: () => teeAuthoringPolicyOp,
|
|
187
|
+
teeReleaseAdmissionPolicyOp: () => teeReleaseAdmissionPolicyOp,
|
|
188
|
+
transferOwnershipOp: () => transferOwnershipOp,
|
|
168
189
|
transportKeyBinding: () => transportKeyBinding,
|
|
169
190
|
trustSignedRelease: () => trustSignedRelease,
|
|
170
191
|
trustedMeasurementsFromReleases: () => trustedMeasurementsFromReleases,
|
|
@@ -1988,6 +2009,29 @@ var AdminApiClient = class {
|
|
|
1988
2009
|
)
|
|
1989
2010
|
);
|
|
1990
2011
|
}
|
|
2012
|
+
/**
|
|
2013
|
+
* Carry the device link of an account **this node does not hold**, so the
|
|
2014
|
+
* namespace binds that device's key to the account.
|
|
2015
|
+
*
|
|
2016
|
+
* The relay half of a nodeless account minting invitations. An account added
|
|
2017
|
+
* to a group by account holds its grants but has no device bound, so peers
|
|
2018
|
+
* refuse whatever its device signs as "bound to no account". The credential
|
|
2019
|
+
* and scope are root-signed and self-certifying; this node adds its own
|
|
2020
|
+
* member endorsement and publishes the `AccountDeviceLinked`. After that the
|
|
2021
|
+
* device signs invitations itself — see `signGroupInvitation`.
|
|
2022
|
+
*
|
|
2023
|
+
* Refusals: `400` when a proof does not verify or the scope does not reach
|
|
2024
|
+
* this namespace (re-sign it), `403` when the device was revoked here or the
|
|
2025
|
+
* account is a member of nothing in the namespace (add it first).
|
|
2026
|
+
*/
|
|
2027
|
+
async linkAccountDevice(namespaceId, request) {
|
|
2028
|
+
return unwrap(
|
|
2029
|
+
await this.httpClient.post(
|
|
2030
|
+
`/admin-api/namespaces/${namespaceId}/account/link-device`,
|
|
2031
|
+
request
|
|
2032
|
+
)
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
1991
2035
|
// ---- Group Management ----
|
|
1992
2036
|
async getGroupInfo(groupId) {
|
|
1993
2037
|
return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}`));
|
|
@@ -2322,9 +2366,45 @@ var AdminApiClient = class {
|
|
|
2322
2366
|
* and admitted TEEs stay members. The node applies the same op as
|
|
2323
2367
|
* `setTeeAuthoringPolicy(groupId, { allowedMrtd: [] })`. `groupId` must be a
|
|
2324
2368
|
* namespace root.
|
|
2369
|
+
*
|
|
2370
|
+
* `rootProof` is the signing admin's own root proof for that empty policy
|
|
2371
|
+
* (`teeAuthoringPolicyOp([])`); omit it on a node that holds that root.
|
|
2372
|
+
*/
|
|
2373
|
+
async disableTeeAuthoringPolicy(groupId, options = {}) {
|
|
2374
|
+
const path = `/admin-api/groups/${groupId}/settings/tee-authoring-policy`;
|
|
2375
|
+
if (options.rootProof === void 0) {
|
|
2376
|
+
await this.httpClient.delete(path);
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
await this.httpClient.request(path, {
|
|
2380
|
+
method: "DELETE",
|
|
2381
|
+
body: JSON.stringify({ rootProof: options.rootProof }),
|
|
2382
|
+
headers: { "Content-Type": "application/json" }
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
// ---- Root-guarded owner ops (core schema 18) ----
|
|
2386
|
+
/**
|
|
2387
|
+
* Hand the group to `newOwner`, who must already be one of its admins.
|
|
2388
|
+
* Owner-only, and it needs the owner account's root proof; see
|
|
2389
|
+
* {@link RootGuardedOpRequest}.
|
|
2390
|
+
*/
|
|
2391
|
+
async transferOwnership(groupId, request) {
|
|
2392
|
+
await this.httpClient.post(`/admin-api/groups/${groupId}/transfer-ownership`, request);
|
|
2393
|
+
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Repoint the namespace's admin pin at `newAdmin`, a member of its root.
|
|
2396
|
+
* Owner-only, with the owner account's root proof; see {@link RootGuardedOpRequest}.
|
|
2397
|
+
*/
|
|
2398
|
+
async changeNamespaceAdmin(namespaceId, request) {
|
|
2399
|
+
await this.httpClient.post(`/admin-api/namespaces/${namespaceId}/admin`, request);
|
|
2400
|
+
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Delete a group through the owner-only path: it must hold no contexts, and
|
|
2403
|
+
* the op needs the owner account's root proof. {@link deleteGroup} is the
|
|
2404
|
+
* admin-level cascading delete.
|
|
2325
2405
|
*/
|
|
2326
|
-
async
|
|
2327
|
-
await this.httpClient.
|
|
2406
|
+
async ownerDeleteGroup(groupId, request = {}) {
|
|
2407
|
+
await this.httpClient.post(`/admin-api/groups/${groupId}/owner-delete`, request);
|
|
2328
2408
|
}
|
|
2329
2409
|
// ---- Group / member / context metadata ----
|
|
2330
2410
|
async setGroupMetadata(groupId, request) {
|
|
@@ -3673,6 +3753,54 @@ async function accountForRootPublicKey(rootPublicKey) {
|
|
|
3673
3753
|
);
|
|
3674
3754
|
}
|
|
3675
3755
|
|
|
3756
|
+
// src/device-cert/device-scope.ts
|
|
3757
|
+
var SCOPE_DOMAIN = new TextEncoder().encode("calimero.device.scope.v1");
|
|
3758
|
+
async function deviceScopePayload(input) {
|
|
3759
|
+
return domainHash(SCOPE_DOMAIN, [
|
|
3760
|
+
fromHex(input.account, "account", 32),
|
|
3761
|
+
fromHex(input.device, "device", 32),
|
|
3762
|
+
u32le(input.scopeEpoch),
|
|
3763
|
+
u32le(input.keyEpoch),
|
|
3764
|
+
...input.applications.map((a, i) => fromHex(a, `applications[${i}]`, 32))
|
|
3765
|
+
]);
|
|
3766
|
+
}
|
|
3767
|
+
async function signDeviceScope(input) {
|
|
3768
|
+
const signer = await resolveSigner(input.rootSecret, input.signer, "rootSecret");
|
|
3769
|
+
const rootPublicKey = fromHex(signer.publicKey, "signer.publicKey", 32);
|
|
3770
|
+
const account = await accountForRootPublicKey(hex(rootPublicKey));
|
|
3771
|
+
const applications = input.applications ?? [];
|
|
3772
|
+
const scopeEpoch = input.scopeEpoch ?? 0;
|
|
3773
|
+
if (!Number.isInteger(scopeEpoch) || scopeEpoch < 0 || scopeEpoch > 4294967295) {
|
|
3774
|
+
throw new Error(`scopeEpoch must be a u32, got ${scopeEpoch}`);
|
|
3775
|
+
}
|
|
3776
|
+
const keyEpoch = 0;
|
|
3777
|
+
const payload = await deviceScopePayload({
|
|
3778
|
+
account,
|
|
3779
|
+
device: input.device,
|
|
3780
|
+
applications,
|
|
3781
|
+
scopeEpoch,
|
|
3782
|
+
keyEpoch
|
|
3783
|
+
});
|
|
3784
|
+
const signature = await signer.sign(payload);
|
|
3785
|
+
if (signature.length !== 64) {
|
|
3786
|
+
throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
|
|
3787
|
+
}
|
|
3788
|
+
return hex(
|
|
3789
|
+
concat(
|
|
3790
|
+
new Uint8Array([ACCOUNT_GENESIS_VERSION]),
|
|
3791
|
+
rootPublicKey,
|
|
3792
|
+
u32le(0),
|
|
3793
|
+
fromHex(account, "account", 32),
|
|
3794
|
+
fromHex(input.device, "device", 32),
|
|
3795
|
+
u32le(applications.length),
|
|
3796
|
+
...applications.map((a, i) => fromHex(a, `applications[${i}]`, 32)),
|
|
3797
|
+
u32le(scopeEpoch),
|
|
3798
|
+
u32le(keyEpoch),
|
|
3799
|
+
signature
|
|
3800
|
+
)
|
|
3801
|
+
);
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3676
3804
|
// src/account/wordlist.ts
|
|
3677
3805
|
var ENGLISH_WORDLIST = [
|
|
3678
3806
|
"abandon",
|
|
@@ -7270,9 +7398,9 @@ function parseCreationWarrant(warrant) {
|
|
|
7270
7398
|
const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
|
|
7271
7399
|
const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
|
|
7272
7400
|
const option = (what) => {
|
|
7273
|
-
const
|
|
7274
|
-
if (
|
|
7275
|
-
if (
|
|
7401
|
+
const tag3 = take(1, what)[0];
|
|
7402
|
+
if (tag3 === 0) return null;
|
|
7403
|
+
if (tag3 !== 1) throw new Error(`${what} has option tag ${tag3}, expected 0 or 1`);
|
|
7276
7404
|
const len = u32(what);
|
|
7277
7405
|
if (len > MAX_LABEL_BYTES) {
|
|
7278
7406
|
throw new Error(`${what} is ${len} bytes, over the ${MAX_LABEL_BYTES} a node accepts`);
|
|
@@ -7516,6 +7644,17 @@ function memberRoleSetOp(member, memberRole) {
|
|
|
7516
7644
|
bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, "member", 32), role(memberRole))
|
|
7517
7645
|
};
|
|
7518
7646
|
}
|
|
7647
|
+
var SUBGROUP_ID_DOMAIN = new TextEncoder().encode("calimero.subgroup.id.v1");
|
|
7648
|
+
async function createdSubgroupId(admin, parentId, restricted, salt) {
|
|
7649
|
+
return hex(
|
|
7650
|
+
await domainHash(SUBGROUP_ID_DOMAIN, [
|
|
7651
|
+
fromHex(admin, "admin", 32),
|
|
7652
|
+
fromHex(parentId, "parentId", 32),
|
|
7653
|
+
new Uint8Array([restricted ? 1 : 0]),
|
|
7654
|
+
fromHex(salt, "salt", 32)
|
|
7655
|
+
])
|
|
7656
|
+
);
|
|
7657
|
+
}
|
|
7519
7658
|
function defaultCapabilitiesSetOp(capabilities) {
|
|
7520
7659
|
if (!Number.isInteger(capabilities) || capabilities < 0 || capabilities > 4294967295) {
|
|
7521
7660
|
throw new Error(`capabilities must be a u32 bit mask, got ${String(capabilities)}`);
|
|
@@ -7538,10 +7677,16 @@ function groupCreatedOp(input) {
|
|
|
7538
7677
|
fromHex(input.groupId, "groupId", 32),
|
|
7539
7678
|
fromHex(input.parentId, "parentId", 32),
|
|
7540
7679
|
tag(input.restricted ? 1 : 0),
|
|
7541
|
-
fromHex(input.admin, "admin", 32)
|
|
7680
|
+
fromHex(input.admin, "admin", 32),
|
|
7681
|
+
fromHex(input.salt, "salt", 32)
|
|
7542
7682
|
)
|
|
7543
7683
|
};
|
|
7544
7684
|
}
|
|
7685
|
+
async function subgroupCreation(input) {
|
|
7686
|
+
const salt = input.salt ?? hex(crypto.getRandomValues(new Uint8Array(32)));
|
|
7687
|
+
const groupId = await createdSubgroupId(input.admin, input.parentId, input.restricted, salt);
|
|
7688
|
+
return { groupId, salt, op: groupCreatedOp({ ...input, groupId, salt }) };
|
|
7689
|
+
}
|
|
7545
7690
|
function groupReparentedOp(childGroupId, newParentId) {
|
|
7546
7691
|
return {
|
|
7547
7692
|
kind: "root",
|
|
@@ -8099,12 +8244,12 @@ function audienceSigningBytes(audience) {
|
|
|
8099
8244
|
);
|
|
8100
8245
|
}
|
|
8101
8246
|
function audienceWire(audience) {
|
|
8102
|
-
const
|
|
8247
|
+
const tag3 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
|
|
8103
8248
|
if (audience.kind === "cli") {
|
|
8104
|
-
return
|
|
8249
|
+
return tag3;
|
|
8105
8250
|
}
|
|
8106
8251
|
const body = audienceBody(audience);
|
|
8107
|
-
return concat(
|
|
8252
|
+
return concat(tag3, u32le(body.length), body);
|
|
8108
8253
|
}
|
|
8109
8254
|
async function signLoginStatement(input) {
|
|
8110
8255
|
const node = fromHex(input.node, "node", 32);
|
|
@@ -9789,8 +9934,8 @@ var SIGNATURE_HASHES = {
|
|
|
9789
9934
|
};
|
|
9790
9935
|
function read(bytes, offset, limit) {
|
|
9791
9936
|
if (offset + 2 > limit) throw new Error("it is truncated");
|
|
9792
|
-
const
|
|
9793
|
-
if ((
|
|
9937
|
+
const tag3 = bytes[offset];
|
|
9938
|
+
if ((tag3 & 31) === 31) throw new Error("it uses a tag this parser does not read");
|
|
9794
9939
|
let length = bytes[offset + 1];
|
|
9795
9940
|
let start = offset + 2;
|
|
9796
9941
|
if (length & 128) {
|
|
@@ -9802,7 +9947,7 @@ function read(bytes, offset, limit) {
|
|
|
9802
9947
|
}
|
|
9803
9948
|
const end = start + length;
|
|
9804
9949
|
if (end > limit) throw new Error("it is truncated");
|
|
9805
|
-
return { tag:
|
|
9950
|
+
return { tag: tag3, bytes, offset, start, end };
|
|
9806
9951
|
}
|
|
9807
9952
|
function parseDer(bytes) {
|
|
9808
9953
|
const element = read(bytes, 0, bytes.length);
|
|
@@ -9818,8 +9963,8 @@ function children(element) {
|
|
|
9818
9963
|
}
|
|
9819
9964
|
return out;
|
|
9820
9965
|
}
|
|
9821
|
-
function expectTag(element,
|
|
9822
|
-
if (!element || element.tag !==
|
|
9966
|
+
function expectTag(element, tag3, what) {
|
|
9967
|
+
if (!element || element.tag !== tag3) throw new Error(`${what} is not where it should be`);
|
|
9823
9968
|
return element;
|
|
9824
9969
|
}
|
|
9825
9970
|
function contents(element) {
|
|
@@ -10281,8 +10426,103 @@ function splitVersion2(version) {
|
|
|
10281
10426
|
return [[Number(match[1]), Number(match[2]), Number(match[3])], match[4]];
|
|
10282
10427
|
}
|
|
10283
10428
|
|
|
10429
|
+
// src/invitation/invitation.ts
|
|
10430
|
+
var MAX_INVITATION_VALIDITY_SECS = 24 * 60 * 60;
|
|
10431
|
+
var INVITED_ROLE = { Admin: 0, Member: 1, ReadOnly: 2 };
|
|
10432
|
+
function encodeGroupInvitation(body) {
|
|
10433
|
+
const admitters = body.admitters ?? [];
|
|
10434
|
+
return concat(
|
|
10435
|
+
// `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`
|
|
10436
|
+
// crosses as hex. Same width, different spelling.
|
|
10437
|
+
bytes32(body.inviter_identity, "inviter_identity"),
|
|
10438
|
+
bytes32(body.group_id, "group_id"),
|
|
10439
|
+
u64le(body.expiration_timestamp),
|
|
10440
|
+
bytes32(body.secret_salt, "secret_salt"),
|
|
10441
|
+
new Uint8Array([body.invited_role]),
|
|
10442
|
+
u32le(admitters.length),
|
|
10443
|
+
...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32))
|
|
10444
|
+
);
|
|
10445
|
+
}
|
|
10446
|
+
async function groupInvitationHash(body) {
|
|
10447
|
+
return new Uint8Array(
|
|
10448
|
+
await crypto.subtle.digest("SHA-256", encodeGroupInvitation(body))
|
|
10449
|
+
);
|
|
10450
|
+
}
|
|
10451
|
+
function defaultAdmitters(members) {
|
|
10452
|
+
const admins = new Set(
|
|
10453
|
+
members.filter((m) => m.role === "Admin").map((m) => m.identity.toLowerCase())
|
|
10454
|
+
);
|
|
10455
|
+
return [...admins].sort();
|
|
10456
|
+
}
|
|
10457
|
+
async function signGroupInvitation(input) {
|
|
10458
|
+
const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
|
|
10459
|
+
const inviterKey = fromHex(signer.publicKey, "signer.publicKey", 32);
|
|
10460
|
+
const groupId = fromHex(input.groupId, "groupId", 32);
|
|
10461
|
+
fromHex(input.inviterAccount, "inviterAccount", 32);
|
|
10462
|
+
let admitters = [...input.admitters ?? []].map((a) => a.trim().toLowerCase());
|
|
10463
|
+
if (admitters.length === 0) {
|
|
10464
|
+
if (!input.members) {
|
|
10465
|
+
throw new Error(
|
|
10466
|
+
"name the admitters, or pass the group members to default them to its admins: an invitation with no admitters is claimable by broadcast"
|
|
10467
|
+
);
|
|
10468
|
+
}
|
|
10469
|
+
admitters = defaultAdmitters(input.members);
|
|
10470
|
+
if (admitters.length === 0) {
|
|
10471
|
+
throw new Error(
|
|
10472
|
+
"the member list names no admin, so there is nobody to default the admitters to"
|
|
10473
|
+
);
|
|
10474
|
+
}
|
|
10475
|
+
}
|
|
10476
|
+
admitters.forEach((a, i) => fromHex(a, `admitters[${i}]`, 32));
|
|
10477
|
+
const invitedRole = input.invitedRole ?? INVITED_ROLE.Member;
|
|
10478
|
+
if (![0, 1, 2].includes(invitedRole)) {
|
|
10479
|
+
throw new Error(`invitedRole must be 0 (Admin), 1 (Member) or 2 (ReadOnly), got ${invitedRole}`);
|
|
10480
|
+
}
|
|
10481
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
10482
|
+
const validFor = Math.min(
|
|
10483
|
+
input.validForSecs ?? MAX_INVITATION_VALIDITY_SECS,
|
|
10484
|
+
MAX_INVITATION_VALIDITY_SECS
|
|
10485
|
+
);
|
|
10486
|
+
if (!Number.isInteger(validFor) || validFor <= 0) {
|
|
10487
|
+
throw new Error(`validForSecs must be a positive whole number, got ${input.validForSecs}`);
|
|
10488
|
+
}
|
|
10489
|
+
const nonce2 = input.nonce ?? crypto.getRandomValues(new Uint8Array(32));
|
|
10490
|
+
if (nonce2.length !== 32) {
|
|
10491
|
+
throw new Error(`nonce must be 32 bytes, got ${nonce2.length}`);
|
|
10492
|
+
}
|
|
10493
|
+
const invitation = {
|
|
10494
|
+
inviter_identity: Array.from(inviterKey),
|
|
10495
|
+
group_id: Array.from(groupId),
|
|
10496
|
+
expiration_timestamp: now + validFor,
|
|
10497
|
+
secret_salt: Array.from(nonce2),
|
|
10498
|
+
invited_role: invitedRole,
|
|
10499
|
+
admitters
|
|
10500
|
+
};
|
|
10501
|
+
const signature = await signer.sign(await groupInvitationHash(invitation));
|
|
10502
|
+
if (signature.length !== 64) {
|
|
10503
|
+
throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
|
|
10504
|
+
}
|
|
10505
|
+
const signed = {
|
|
10506
|
+
invitation,
|
|
10507
|
+
inviter_signature: hex(signature),
|
|
10508
|
+
inviter_account: input.inviterAccount.trim().toLowerCase()
|
|
10509
|
+
};
|
|
10510
|
+
if (input.admitterAddrs?.length) signed.admitter_addrs = [...input.admitterAddrs];
|
|
10511
|
+
if (input.applicationId) {
|
|
10512
|
+
signed.application_id = Array.from(fromHex(input.applicationId, "applicationId", 32));
|
|
10513
|
+
}
|
|
10514
|
+
if (input.appKey) signed.app_key = Array.from(fromHex(input.appKey, "appKey", 32));
|
|
10515
|
+
return signed;
|
|
10516
|
+
}
|
|
10517
|
+
function bytes32(value, label2) {
|
|
10518
|
+
if (value.length !== 32) {
|
|
10519
|
+
throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
|
|
10520
|
+
}
|
|
10521
|
+
return new Uint8Array(value);
|
|
10522
|
+
}
|
|
10523
|
+
|
|
10284
10524
|
// src/namespace-op/namespace-op.ts
|
|
10285
|
-
var SIGNED_NAMESPACE_OP_SCHEMA_VERSION =
|
|
10525
|
+
var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 20;
|
|
10286
10526
|
var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
|
|
10287
10527
|
var NAMESPACE_OP_ROOT = 0;
|
|
10288
10528
|
var ROOT_OP2 = {
|
|
@@ -10298,12 +10538,6 @@ var ROOT_OP2 = {
|
|
|
10298
10538
|
NamespaceCreatedV2: 9,
|
|
10299
10539
|
MemberJoinedViaTeeAttestation: 10
|
|
10300
10540
|
};
|
|
10301
|
-
function bytes32(value, label2) {
|
|
10302
|
-
if (value.length !== 32) {
|
|
10303
|
-
throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
|
|
10304
|
-
}
|
|
10305
|
-
return new Uint8Array(value);
|
|
10306
|
-
}
|
|
10307
10541
|
function optionalBytes32(value, label2) {
|
|
10308
10542
|
if (value === null || value === void 0) {
|
|
10309
10543
|
return new Uint8Array([0]);
|
|
@@ -10319,21 +10553,11 @@ function borshString2(value) {
|
|
|
10319
10553
|
}
|
|
10320
10554
|
function encodeSignedInvitation(signed) {
|
|
10321
10555
|
const body = signed.invitation;
|
|
10322
|
-
const admitters = body.admitters ?? [];
|
|
10323
10556
|
const admitterAddrs = signed.admitter_addrs ?? [];
|
|
10324
10557
|
return concat(
|
|
10325
|
-
// GroupInvitationFromAdmin
|
|
10326
|
-
//
|
|
10327
|
-
|
|
10328
|
-
// crosses as hex. Same width, different spelling, and reading either the
|
|
10329
|
-
// wrong way yields 32 bytes that encode cleanly and verify as nothing.
|
|
10330
|
-
bytes32(body.inviter_identity, "inviter_identity"),
|
|
10331
|
-
bytes32(body.group_id, "group_id"),
|
|
10332
|
-
u64le(body.expiration_timestamp),
|
|
10333
|
-
bytes32(body.secret_salt, "secret_salt"),
|
|
10334
|
-
new Uint8Array([body.invited_role]),
|
|
10335
|
-
u32le(admitters.length),
|
|
10336
|
-
...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32)),
|
|
10558
|
+
// GroupInvitationFromAdmin, from the one encoder the SDK's own invitation
|
|
10559
|
+
// signer uses: the bytes signed and the bytes carried must not drift.
|
|
10560
|
+
encodeGroupInvitation(body),
|
|
10337
10561
|
// ...then the enclosing SignedGroupOpenInvitation, in declaration order.
|
|
10338
10562
|
// `inviter_account` sits between the signature and the addresses; putting it
|
|
10339
10563
|
// anywhere else still encodes 32 bytes and still verifies as nothing.
|
|
@@ -10388,6 +10612,183 @@ async function signMemberJoinOp(input) {
|
|
|
10388
10612
|
return hex(concat(signable, signature, unendorsed));
|
|
10389
10613
|
}
|
|
10390
10614
|
|
|
10615
|
+
// src/owner-op/owner-op.ts
|
|
10616
|
+
var OWNER_OP_SIGN_DOMAIN = new TextEncoder().encode("calimero.account.owner-op.v1");
|
|
10617
|
+
var OWNER_OP_BODY_DOMAIN = new TextEncoder().encode("calimero.account.owner-op.body.v1");
|
|
10618
|
+
var ACCOUNT_ID_DOMAIN2 = new TextEncoder().encode("calimero.account.genesis.v1");
|
|
10619
|
+
var ACCOUNT_GENESIS_VERSION2 = 2;
|
|
10620
|
+
var OWNER_OP_KIND = {
|
|
10621
|
+
transferOwnership: 0,
|
|
10622
|
+
adminChanged: 1,
|
|
10623
|
+
groupDelete: 2,
|
|
10624
|
+
teeAdmissionPolicy: 3,
|
|
10625
|
+
teeAuthoringPolicy: 4,
|
|
10626
|
+
teeReleaseAdmissionPolicy: 5
|
|
10627
|
+
};
|
|
10628
|
+
var GROUP_OP2 = {
|
|
10629
|
+
GroupDelete: 14,
|
|
10630
|
+
TransferOwnership: 21,
|
|
10631
|
+
TeeAuthoringPolicySet: 33,
|
|
10632
|
+
TeeAdmissionPolicySetV2: 37,
|
|
10633
|
+
TeeReleaseAdmissionPolicySetV2: 38,
|
|
10634
|
+
RootGuarded: 42
|
|
10635
|
+
};
|
|
10636
|
+
var ROOT_OP3 = {
|
|
10637
|
+
AdminChanged: 3,
|
|
10638
|
+
RootGuarded: 12
|
|
10639
|
+
};
|
|
10640
|
+
var TEE_MODE = { replica: 0, relay: 1 };
|
|
10641
|
+
var te = new TextEncoder();
|
|
10642
|
+
function tag2(byte) {
|
|
10643
|
+
return new Uint8Array([byte]);
|
|
10644
|
+
}
|
|
10645
|
+
function borshString3(value) {
|
|
10646
|
+
const bytes = te.encode(value);
|
|
10647
|
+
return concat(u32le(bytes.length), bytes);
|
|
10648
|
+
}
|
|
10649
|
+
function borshStrings(values) {
|
|
10650
|
+
return concat(u32le(values.length), ...values.map(borshString3));
|
|
10651
|
+
}
|
|
10652
|
+
function borshBool(value) {
|
|
10653
|
+
return tag2(value ? 1 : 0);
|
|
10654
|
+
}
|
|
10655
|
+
function teeMode(mode) {
|
|
10656
|
+
const byte = TEE_MODE[mode ?? "replica"];
|
|
10657
|
+
if (byte === void 0) {
|
|
10658
|
+
throw new Error(`mode must be replica or relay, got ${String(mode)}`);
|
|
10659
|
+
}
|
|
10660
|
+
return tag2(byte);
|
|
10661
|
+
}
|
|
10662
|
+
function transferOwnershipOp(newOwner) {
|
|
10663
|
+
return {
|
|
10664
|
+
kind: "transferOwnership",
|
|
10665
|
+
plane: "group",
|
|
10666
|
+
bytes: concat(tag2(GROUP_OP2.TransferOwnership), fromHex(newOwner, "newOwner", 32))
|
|
10667
|
+
};
|
|
10668
|
+
}
|
|
10669
|
+
function groupDeleteOp() {
|
|
10670
|
+
return { kind: "groupDelete", plane: "group", bytes: tag2(GROUP_OP2.GroupDelete) };
|
|
10671
|
+
}
|
|
10672
|
+
function adminChangedOp(newAdmin) {
|
|
10673
|
+
return {
|
|
10674
|
+
kind: "adminChanged",
|
|
10675
|
+
plane: "root",
|
|
10676
|
+
bytes: concat(tag2(ROOT_OP3.AdminChanged), fromHex(newAdmin, "newAdmin", 32))
|
|
10677
|
+
};
|
|
10678
|
+
}
|
|
10679
|
+
function teeAuthoringPolicyOp(allowedMrtd) {
|
|
10680
|
+
return {
|
|
10681
|
+
kind: "teeAuthoringPolicy",
|
|
10682
|
+
plane: "group",
|
|
10683
|
+
bytes: concat(tag2(GROUP_OP2.TeeAuthoringPolicySet), borshStrings(allowedMrtd))
|
|
10684
|
+
};
|
|
10685
|
+
}
|
|
10686
|
+
function teeAdmissionPolicyOp(input) {
|
|
10687
|
+
return {
|
|
10688
|
+
kind: "teeAdmissionPolicy",
|
|
10689
|
+
plane: "group",
|
|
10690
|
+
bytes: concat(
|
|
10691
|
+
tag2(GROUP_OP2.TeeAdmissionPolicySetV2),
|
|
10692
|
+
borshStrings(input.allowedMrtd),
|
|
10693
|
+
borshStrings(input.allowedRtmr0),
|
|
10694
|
+
borshStrings(input.allowedRtmr1),
|
|
10695
|
+
borshStrings(input.allowedRtmr2),
|
|
10696
|
+
borshStrings(input.allowedRtmr3),
|
|
10697
|
+
borshStrings(input.allowedTcbStatuses),
|
|
10698
|
+
borshBool(input.acceptMock),
|
|
10699
|
+
teeMode(input.mode)
|
|
10700
|
+
)
|
|
10701
|
+
};
|
|
10702
|
+
}
|
|
10703
|
+
function teeReleaseAdmissionPolicyOp(input) {
|
|
10704
|
+
const minRelease = input.minReleaseVersion === void 0 ? tag2(0) : concat(tag2(1), borshString3(input.minReleaseVersion));
|
|
10705
|
+
return {
|
|
10706
|
+
kind: "teeReleaseAdmissionPolicy",
|
|
10707
|
+
plane: "group",
|
|
10708
|
+
bytes: concat(
|
|
10709
|
+
tag2(GROUP_OP2.TeeReleaseAdmissionPolicySetV2),
|
|
10710
|
+
borshStrings(input.allowedProfiles),
|
|
10711
|
+
minRelease,
|
|
10712
|
+
borshStrings(input.allowedTcbStatuses),
|
|
10713
|
+
borshBool(input.acceptMock),
|
|
10714
|
+
teeMode(input.mode)
|
|
10715
|
+
)
|
|
10716
|
+
};
|
|
10717
|
+
}
|
|
10718
|
+
async function ownerOpDigest(opBytes) {
|
|
10719
|
+
return domainHash(OWNER_OP_BODY_DOMAIN, [opBytes]);
|
|
10720
|
+
}
|
|
10721
|
+
async function ownerOpSigningPayload(input) {
|
|
10722
|
+
return domainHash(OWNER_OP_SIGN_DOMAIN, [
|
|
10723
|
+
fromHex(input.account, "account", 32),
|
|
10724
|
+
fromHex(input.namespaceId, "namespaceId", 32),
|
|
10725
|
+
fromHex(input.groupId, "groupId", 32),
|
|
10726
|
+
tag2(kindByte2(input.kind)),
|
|
10727
|
+
input.opDigest,
|
|
10728
|
+
u64le(input.counter),
|
|
10729
|
+
u32le(input.keyEpoch)
|
|
10730
|
+
]);
|
|
10731
|
+
}
|
|
10732
|
+
function kindByte2(kind) {
|
|
10733
|
+
const byte = OWNER_OP_KIND[kind];
|
|
10734
|
+
if (byte === void 0) {
|
|
10735
|
+
throw new Error(`unknown owner-op kind ${String(kind)}`);
|
|
10736
|
+
}
|
|
10737
|
+
return byte;
|
|
10738
|
+
}
|
|
10739
|
+
var HANDOFF_BYTES = 32 + 4 + 32 + 64;
|
|
10740
|
+
async function signOwnerOpProof(input) {
|
|
10741
|
+
const rootPublicKey = await derivePublicKey(input.rootSecret);
|
|
10742
|
+
const genesis = concat(tag2(ACCOUNT_GENESIS_VERSION2), rootPublicKey);
|
|
10743
|
+
const account = hex(await domainHash(ACCOUNT_ID_DOMAIN2, [genesis]));
|
|
10744
|
+
const keyEpoch = input.keyEpoch ?? 0;
|
|
10745
|
+
const chain = (input.chain ?? []).map((handoff, index) => {
|
|
10746
|
+
const bytes = fromHexUnsized(handoff, `chain[${index}]`);
|
|
10747
|
+
if (bytes.length !== HANDOFF_BYTES) {
|
|
10748
|
+
throw new Error(
|
|
10749
|
+
`chain[${index}] must be a ${HANDOFF_BYTES}-byte RootKeyHandoff, got ${bytes.length}`
|
|
10750
|
+
);
|
|
10751
|
+
}
|
|
10752
|
+
return bytes;
|
|
10753
|
+
});
|
|
10754
|
+
if (keyEpoch > chain.length) {
|
|
10755
|
+
throw new Error(
|
|
10756
|
+
`keyEpoch ${keyEpoch} needs ${keyEpoch} handoffs in chain, got ${chain.length}`
|
|
10757
|
+
);
|
|
10758
|
+
}
|
|
10759
|
+
const opDigest = await ownerOpDigest(input.op.bytes);
|
|
10760
|
+
const payload = await ownerOpSigningPayload({
|
|
10761
|
+
account,
|
|
10762
|
+
namespaceId: input.namespaceId,
|
|
10763
|
+
groupId: input.groupId,
|
|
10764
|
+
kind: input.op.kind,
|
|
10765
|
+
opDigest,
|
|
10766
|
+
counter: input.counter,
|
|
10767
|
+
keyEpoch
|
|
10768
|
+
});
|
|
10769
|
+
const key = await importSigningKey(input.rootSecret);
|
|
10770
|
+
const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", key, payload));
|
|
10771
|
+
return hex(
|
|
10772
|
+
concat(
|
|
10773
|
+
genesis,
|
|
10774
|
+
u32le(chain.length),
|
|
10775
|
+
...chain,
|
|
10776
|
+
fromHex(account, "account", 32),
|
|
10777
|
+
fromHex(input.namespaceId, "namespaceId", 32),
|
|
10778
|
+
fromHex(input.groupId, "groupId", 32),
|
|
10779
|
+
tag2(kindByte2(input.op.kind)),
|
|
10780
|
+
opDigest,
|
|
10781
|
+
u64le(input.counter),
|
|
10782
|
+
u32le(keyEpoch),
|
|
10783
|
+
signature
|
|
10784
|
+
)
|
|
10785
|
+
);
|
|
10786
|
+
}
|
|
10787
|
+
function rootGuardedOpBytes(op, rootProof) {
|
|
10788
|
+
const wrapper = op.plane === "group" ? GROUP_OP2.RootGuarded : ROOT_OP3.RootGuarded;
|
|
10789
|
+
return concat(tag2(wrapper), op.bytes, fromHexUnsized(rootProof, "rootProof"));
|
|
10790
|
+
}
|
|
10791
|
+
|
|
10391
10792
|
// src/nodeDiscovery.ts
|
|
10392
10793
|
var DEFAULT_LOCAL_NODE_PORTS = [2428, 2429, 2528, 2529];
|
|
10393
10794
|
var LOCAL_HOST = "localhost";
|