@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/README.md +9 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +570 -29
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +570 -29
- package/dist/index.mjs.map +4 -4
- package/dist/namespace-op/namespace-op.d.ts +8 -4
- package/dist/namespace-op/namespace-op.js +8 -4
- package/dist/relay/index.d.ts +1 -1
- package/dist/relay/relay-client.d.ts +132 -0
- package/dist/relay/relay-client.js +132 -0
- package/dist/warrant/creation-warrant.d.ts +145 -0
- package/dist/warrant/creation-warrant.js +189 -0
- package/dist/warrant/governance-op.d.ts +81 -0
- package/dist/warrant/governance-op.js +125 -0
- package/dist/warrant/governance-warrant.d.ts +106 -0
- package/dist/warrant/governance-warrant.js +159 -0
- package/dist/warrant/index.d.ts +6 -0
- package/dist/warrant/index.js +3 -0
- package/dist/warrant/warrant.d.ts +5 -0
- package/dist/warrant/warrant.js +5 -2
- package/package.json +1 -1
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,
|
|
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
|
-
`${
|
|
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,
|
|
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(`${
|
|
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,
|
|
6652
|
+
function citedHeads(heads, label2) {
|
|
6640
6653
|
const list = heads ?? [];
|
|
6641
6654
|
if (list.length > MAX_CITED_HEADS) {
|
|
6642
6655
|
throw new Error(
|
|
6643
|
-
`${
|
|
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, `${
|
|
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
|
|
7409
|
-
return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${
|
|
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,
|
|
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(`${
|
|
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
|
|
7596
|
-
if ((
|
|
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,
|
|
7625
|
-
if (!element || element.tag !==
|
|
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 =
|
|
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
|
|
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,
|
|
8645
|
+
function bytes32(value, label2) {
|
|
8105
8646
|
if (value.length !== 32) {
|
|
8106
|
-
throw new Error(`${
|
|
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,
|
|
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(`${
|
|
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([
|
|
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([
|
|
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
|
|
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
|
|
8762
|
+
const tag2 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
|
|
8222
8763
|
if (audience.kind === "cli") {
|
|
8223
|
-
return
|
|
8764
|
+
return tag2;
|
|
8224
8765
|
}
|
|
8225
8766
|
const body = audienceBody(audience);
|
|
8226
|
-
return concat(
|
|
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(
|
|
8777
|
+
const preimage = await domainHash(SIGN_DOMAIN5, [
|
|
8237
8778
|
node,
|
|
8238
8779
|
audienceSigningBytes(input.audience),
|
|
8239
8780
|
challenge,
|