@calimero-network/mero-js 22.8.0 → 22.9.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 +17 -1
- package/dist/admin-api/admin-client.js +18 -0
- package/dist/admin-api/admin-types.d.ts +34 -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 +177 -19
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.mjs +177 -19
- 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.js +4 -14
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,8 +30,10 @@ __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,
|
|
@@ -105,11 +107,14 @@ __export(index_exports, {
|
|
|
105
107
|
createTimeoutSignal: () => createTimeoutSignal,
|
|
106
108
|
createUniversalHttpClient: () => createUniversalHttpClient,
|
|
107
109
|
creationInitHash: () => creationInitHash,
|
|
110
|
+
defaultAdmitters: () => defaultAdmitters,
|
|
108
111
|
defaultAudience: () => defaultAudience,
|
|
109
112
|
defaultCapabilitiesSetOp: () => defaultCapabilitiesSetOp,
|
|
110
113
|
deviceCertPayload: () => deviceCertPayload,
|
|
111
114
|
deviceEnrolmentUrl: () => deviceEnrolmentUrl,
|
|
115
|
+
deviceScopePayload: () => deviceScopePayload,
|
|
112
116
|
discoverLocalNodes: () => discoverLocalNodes,
|
|
117
|
+
encodeGroupInvitation: () => encodeGroupInvitation,
|
|
113
118
|
encodeSignedInvitation: () => encodeSignedInvitation,
|
|
114
119
|
fetchAttestedTransportKey: () => fetchAttestedTransportKey,
|
|
115
120
|
fetchNodeRelease: () => fetchNodeRelease,
|
|
@@ -120,6 +125,7 @@ __export(index_exports, {
|
|
|
120
125
|
governanceOpHash: () => governanceOpHash,
|
|
121
126
|
groupCreatedOp: () => groupCreatedOp,
|
|
122
127
|
groupDeletedOp: () => groupDeletedOp,
|
|
128
|
+
groupInvitationHash: () => groupInvitationHash,
|
|
123
129
|
groupReparentedOp: () => groupReparentedOp,
|
|
124
130
|
hasCap: () => hasCap,
|
|
125
131
|
hexEncodeUtf8: () => hexEncodeUtf8,
|
|
@@ -155,7 +161,9 @@ __export(index_exports, {
|
|
|
155
161
|
signAccountLogin: () => signAccountLogin,
|
|
156
162
|
signCreationWarrant: () => signCreationWarrant,
|
|
157
163
|
signDeviceCert: () => signDeviceCert,
|
|
164
|
+
signDeviceScope: () => signDeviceScope,
|
|
158
165
|
signGovernanceWarrant: () => signGovernanceWarrant,
|
|
166
|
+
signGroupInvitation: () => signGroupInvitation,
|
|
159
167
|
signLoginStatement: () => signLoginStatement,
|
|
160
168
|
signMemberJoinOp: () => signMemberJoinOp,
|
|
161
169
|
signRequest: () => signRequest,
|
|
@@ -1988,6 +1996,29 @@ var AdminApiClient = class {
|
|
|
1988
1996
|
)
|
|
1989
1997
|
);
|
|
1990
1998
|
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Carry the device link of an account **this node does not hold**, so the
|
|
2001
|
+
* namespace binds that device's key to the account.
|
|
2002
|
+
*
|
|
2003
|
+
* The relay half of a nodeless account minting invitations. An account added
|
|
2004
|
+
* to a group by account holds its grants but has no device bound, so peers
|
|
2005
|
+
* refuse whatever its device signs as "bound to no account". The credential
|
|
2006
|
+
* and scope are root-signed and self-certifying; this node adds its own
|
|
2007
|
+
* member endorsement and publishes the `AccountDeviceLinked`. After that the
|
|
2008
|
+
* device signs invitations itself — see `signGroupInvitation`.
|
|
2009
|
+
*
|
|
2010
|
+
* Refusals: `400` when a proof does not verify or the scope does not reach
|
|
2011
|
+
* this namespace (re-sign it), `403` when the device was revoked here or the
|
|
2012
|
+
* account is a member of nothing in the namespace (add it first).
|
|
2013
|
+
*/
|
|
2014
|
+
async linkAccountDevice(namespaceId, request) {
|
|
2015
|
+
return unwrap(
|
|
2016
|
+
await this.httpClient.post(
|
|
2017
|
+
`/admin-api/namespaces/${namespaceId}/account/link-device`,
|
|
2018
|
+
request
|
|
2019
|
+
)
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
1991
2022
|
// ---- Group Management ----
|
|
1992
2023
|
async getGroupInfo(groupId) {
|
|
1993
2024
|
return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}`));
|
|
@@ -3673,6 +3704,54 @@ async function accountForRootPublicKey(rootPublicKey) {
|
|
|
3673
3704
|
);
|
|
3674
3705
|
}
|
|
3675
3706
|
|
|
3707
|
+
// src/device-cert/device-scope.ts
|
|
3708
|
+
var SCOPE_DOMAIN = new TextEncoder().encode("calimero.device.scope.v1");
|
|
3709
|
+
async function deviceScopePayload(input) {
|
|
3710
|
+
return domainHash(SCOPE_DOMAIN, [
|
|
3711
|
+
fromHex(input.account, "account", 32),
|
|
3712
|
+
fromHex(input.device, "device", 32),
|
|
3713
|
+
u32le(input.scopeEpoch),
|
|
3714
|
+
u32le(input.keyEpoch),
|
|
3715
|
+
...input.applications.map((a, i) => fromHex(a, `applications[${i}]`, 32))
|
|
3716
|
+
]);
|
|
3717
|
+
}
|
|
3718
|
+
async function signDeviceScope(input) {
|
|
3719
|
+
const signer = await resolveSigner(input.rootSecret, input.signer, "rootSecret");
|
|
3720
|
+
const rootPublicKey = fromHex(signer.publicKey, "signer.publicKey", 32);
|
|
3721
|
+
const account = await accountForRootPublicKey(hex(rootPublicKey));
|
|
3722
|
+
const applications = input.applications ?? [];
|
|
3723
|
+
const scopeEpoch = input.scopeEpoch ?? 0;
|
|
3724
|
+
if (!Number.isInteger(scopeEpoch) || scopeEpoch < 0 || scopeEpoch > 4294967295) {
|
|
3725
|
+
throw new Error(`scopeEpoch must be a u32, got ${scopeEpoch}`);
|
|
3726
|
+
}
|
|
3727
|
+
const keyEpoch = 0;
|
|
3728
|
+
const payload = await deviceScopePayload({
|
|
3729
|
+
account,
|
|
3730
|
+
device: input.device,
|
|
3731
|
+
applications,
|
|
3732
|
+
scopeEpoch,
|
|
3733
|
+
keyEpoch
|
|
3734
|
+
});
|
|
3735
|
+
const signature = await signer.sign(payload);
|
|
3736
|
+
if (signature.length !== 64) {
|
|
3737
|
+
throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
|
|
3738
|
+
}
|
|
3739
|
+
return hex(
|
|
3740
|
+
concat(
|
|
3741
|
+
new Uint8Array([ACCOUNT_GENESIS_VERSION]),
|
|
3742
|
+
rootPublicKey,
|
|
3743
|
+
u32le(0),
|
|
3744
|
+
fromHex(account, "account", 32),
|
|
3745
|
+
fromHex(input.device, "device", 32),
|
|
3746
|
+
u32le(applications.length),
|
|
3747
|
+
...applications.map((a, i) => fromHex(a, `applications[${i}]`, 32)),
|
|
3748
|
+
u32le(scopeEpoch),
|
|
3749
|
+
u32le(keyEpoch),
|
|
3750
|
+
signature
|
|
3751
|
+
)
|
|
3752
|
+
);
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3676
3755
|
// src/account/wordlist.ts
|
|
3677
3756
|
var ENGLISH_WORDLIST = [
|
|
3678
3757
|
"abandon",
|
|
@@ -10281,6 +10360,101 @@ function splitVersion2(version) {
|
|
|
10281
10360
|
return [[Number(match[1]), Number(match[2]), Number(match[3])], match[4]];
|
|
10282
10361
|
}
|
|
10283
10362
|
|
|
10363
|
+
// src/invitation/invitation.ts
|
|
10364
|
+
var MAX_INVITATION_VALIDITY_SECS = 24 * 60 * 60;
|
|
10365
|
+
var INVITED_ROLE = { Admin: 0, Member: 1, ReadOnly: 2 };
|
|
10366
|
+
function encodeGroupInvitation(body) {
|
|
10367
|
+
const admitters = body.admitters ?? [];
|
|
10368
|
+
return concat(
|
|
10369
|
+
// `SignerId` and `ContextGroupId` cross JSON as 32 numbers; `AccountId`
|
|
10370
|
+
// crosses as hex. Same width, different spelling.
|
|
10371
|
+
bytes32(body.inviter_identity, "inviter_identity"),
|
|
10372
|
+
bytes32(body.group_id, "group_id"),
|
|
10373
|
+
u64le(body.expiration_timestamp),
|
|
10374
|
+
bytes32(body.secret_salt, "secret_salt"),
|
|
10375
|
+
new Uint8Array([body.invited_role]),
|
|
10376
|
+
u32le(admitters.length),
|
|
10377
|
+
...admitters.map((a, i) => fromHex(a, `admitters[${i}]`, 32))
|
|
10378
|
+
);
|
|
10379
|
+
}
|
|
10380
|
+
async function groupInvitationHash(body) {
|
|
10381
|
+
return new Uint8Array(
|
|
10382
|
+
await crypto.subtle.digest("SHA-256", encodeGroupInvitation(body))
|
|
10383
|
+
);
|
|
10384
|
+
}
|
|
10385
|
+
function defaultAdmitters(members) {
|
|
10386
|
+
const admins = new Set(
|
|
10387
|
+
members.filter((m) => m.role === "Admin").map((m) => m.identity.toLowerCase())
|
|
10388
|
+
);
|
|
10389
|
+
return [...admins].sort();
|
|
10390
|
+
}
|
|
10391
|
+
async function signGroupInvitation(input) {
|
|
10392
|
+
const signer = await resolveSigner(input.deviceSecret, input.signer, "deviceSecret");
|
|
10393
|
+
const inviterKey = fromHex(signer.publicKey, "signer.publicKey", 32);
|
|
10394
|
+
const groupId = fromHex(input.groupId, "groupId", 32);
|
|
10395
|
+
fromHex(input.inviterAccount, "inviterAccount", 32);
|
|
10396
|
+
let admitters = [...input.admitters ?? []].map((a) => a.trim().toLowerCase());
|
|
10397
|
+
if (admitters.length === 0) {
|
|
10398
|
+
if (!input.members) {
|
|
10399
|
+
throw new Error(
|
|
10400
|
+
"name the admitters, or pass the group members to default them to its admins: an invitation with no admitters is claimable by broadcast"
|
|
10401
|
+
);
|
|
10402
|
+
}
|
|
10403
|
+
admitters = defaultAdmitters(input.members);
|
|
10404
|
+
if (admitters.length === 0) {
|
|
10405
|
+
throw new Error(
|
|
10406
|
+
"the member list names no admin, so there is nobody to default the admitters to"
|
|
10407
|
+
);
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
admitters.forEach((a, i) => fromHex(a, `admitters[${i}]`, 32));
|
|
10411
|
+
const invitedRole = input.invitedRole ?? INVITED_ROLE.Member;
|
|
10412
|
+
if (![0, 1, 2].includes(invitedRole)) {
|
|
10413
|
+
throw new Error(`invitedRole must be 0 (Admin), 1 (Member) or 2 (ReadOnly), got ${invitedRole}`);
|
|
10414
|
+
}
|
|
10415
|
+
const now = input.now ?? Math.floor(Date.now() / 1e3);
|
|
10416
|
+
const validFor = Math.min(
|
|
10417
|
+
input.validForSecs ?? MAX_INVITATION_VALIDITY_SECS,
|
|
10418
|
+
MAX_INVITATION_VALIDITY_SECS
|
|
10419
|
+
);
|
|
10420
|
+
if (!Number.isInteger(validFor) || validFor <= 0) {
|
|
10421
|
+
throw new Error(`validForSecs must be a positive whole number, got ${input.validForSecs}`);
|
|
10422
|
+
}
|
|
10423
|
+
const nonce2 = input.nonce ?? crypto.getRandomValues(new Uint8Array(32));
|
|
10424
|
+
if (nonce2.length !== 32) {
|
|
10425
|
+
throw new Error(`nonce must be 32 bytes, got ${nonce2.length}`);
|
|
10426
|
+
}
|
|
10427
|
+
const invitation = {
|
|
10428
|
+
inviter_identity: Array.from(inviterKey),
|
|
10429
|
+
group_id: Array.from(groupId),
|
|
10430
|
+
expiration_timestamp: now + validFor,
|
|
10431
|
+
secret_salt: Array.from(nonce2),
|
|
10432
|
+
invited_role: invitedRole,
|
|
10433
|
+
admitters
|
|
10434
|
+
};
|
|
10435
|
+
const signature = await signer.sign(await groupInvitationHash(invitation));
|
|
10436
|
+
if (signature.length !== 64) {
|
|
10437
|
+
throw new Error(`signer returned ${signature.length} bytes, expected a 64-byte Ed25519 signature`);
|
|
10438
|
+
}
|
|
10439
|
+
const signed = {
|
|
10440
|
+
invitation,
|
|
10441
|
+
inviter_signature: hex(signature),
|
|
10442
|
+
inviter_account: input.inviterAccount.trim().toLowerCase()
|
|
10443
|
+
};
|
|
10444
|
+
if (input.admitterAddrs?.length) signed.admitter_addrs = [...input.admitterAddrs];
|
|
10445
|
+
if (input.applicationId) {
|
|
10446
|
+
signed.application_id = Array.from(fromHex(input.applicationId, "applicationId", 32));
|
|
10447
|
+
}
|
|
10448
|
+
if (input.appKey) signed.app_key = Array.from(fromHex(input.appKey, "appKey", 32));
|
|
10449
|
+
return signed;
|
|
10450
|
+
}
|
|
10451
|
+
function bytes32(value, label2) {
|
|
10452
|
+
if (value.length !== 32) {
|
|
10453
|
+
throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
|
|
10454
|
+
}
|
|
10455
|
+
return new Uint8Array(value);
|
|
10456
|
+
}
|
|
10457
|
+
|
|
10284
10458
|
// src/namespace-op/namespace-op.ts
|
|
10285
10459
|
var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 16;
|
|
10286
10460
|
var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
|
|
@@ -10298,12 +10472,6 @@ var ROOT_OP2 = {
|
|
|
10298
10472
|
NamespaceCreatedV2: 9,
|
|
10299
10473
|
MemberJoinedViaTeeAttestation: 10
|
|
10300
10474
|
};
|
|
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
10475
|
function optionalBytes32(value, label2) {
|
|
10308
10476
|
if (value === null || value === void 0) {
|
|
10309
10477
|
return new Uint8Array([0]);
|
|
@@ -10319,21 +10487,11 @@ function borshString2(value) {
|
|
|
10319
10487
|
}
|
|
10320
10488
|
function encodeSignedInvitation(signed) {
|
|
10321
10489
|
const body = signed.invitation;
|
|
10322
|
-
const admitters = body.admitters ?? [];
|
|
10323
10490
|
const admitterAddrs = signed.admitter_addrs ?? [];
|
|
10324
10491
|
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)),
|
|
10492
|
+
// GroupInvitationFromAdmin, from the one encoder the SDK's own invitation
|
|
10493
|
+
// signer uses: the bytes signed and the bytes carried must not drift.
|
|
10494
|
+
encodeGroupInvitation(body),
|
|
10337
10495
|
// ...then the enclosing SignedGroupOpenInvitation, in declaration order.
|
|
10338
10496
|
// `inviter_account` sits between the signature and the addresses; putting it
|
|
10339
10497
|
// anywhere else still encodes 32 bytes and still verifies as nothing.
|