@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.mjs
CHANGED
|
@@ -2183,11 +2183,11 @@ function u32le(value) {
|
|
|
2183
2183
|
function hex(bytes) {
|
|
2184
2184
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2185
2185
|
}
|
|
2186
|
-
function fromHex(value,
|
|
2186
|
+
function fromHex(value, label2, expectedBytes) {
|
|
2187
2187
|
const clean = value.trim();
|
|
2188
2188
|
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length !== expectedBytes * 2) {
|
|
2189
2189
|
throw new Error(
|
|
2190
|
-
`${
|
|
2190
|
+
`${label2} must be ${expectedBytes * 2} hex characters, got ${clean.length}`
|
|
2191
2191
|
);
|
|
2192
2192
|
}
|
|
2193
2193
|
const bytes = new Uint8Array(expectedBytes);
|
|
@@ -2196,10 +2196,10 @@ function fromHex(value, label, expectedBytes) {
|
|
|
2196
2196
|
}
|
|
2197
2197
|
return bytes;
|
|
2198
2198
|
}
|
|
2199
|
-
function fromHexUnsized(value,
|
|
2199
|
+
function fromHexUnsized(value, label2) {
|
|
2200
2200
|
const clean = value.trim();
|
|
2201
2201
|
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
2202
|
-
throw new Error(`${
|
|
2202
|
+
throw new Error(`${label2} must be an even number of hex characters`);
|
|
2203
2203
|
}
|
|
2204
2204
|
const bytes = new Uint8Array(clean.length / 2);
|
|
2205
2205
|
for (let i = 0; i < bytes.length; i += 1) {
|
|
@@ -6513,14 +6513,300 @@ async function signWarrant(input) {
|
|
|
6513
6513
|
)
|
|
6514
6514
|
);
|
|
6515
6515
|
}
|
|
6516
|
-
function citedHeads(heads,
|
|
6516
|
+
function citedHeads(heads, label2) {
|
|
6517
6517
|
const list = heads ?? [];
|
|
6518
6518
|
if (list.length > MAX_CITED_HEADS) {
|
|
6519
6519
|
throw new Error(
|
|
6520
|
-
`${
|
|
6520
|
+
`${label2} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`
|
|
6521
6521
|
);
|
|
6522
6522
|
}
|
|
6523
|
-
return list.map((head, i) => fromHex(head, `${
|
|
6523
|
+
return list.map((head, i) => fromHex(head, `${label2}[${i}]`, 32));
|
|
6524
|
+
}
|
|
6525
|
+
|
|
6526
|
+
// src/warrant/creation-warrant.ts
|
|
6527
|
+
var SIGN_DOMAIN3 = new TextEncoder().encode("calimero.context-creation-warrant.v1");
|
|
6528
|
+
var INIT_DOMAIN = new TextEncoder().encode("calimero.context-creation.init.v1");
|
|
6529
|
+
var MAX_LABEL_BYTES = 256;
|
|
6530
|
+
async function creationInitHash(initArgs) {
|
|
6531
|
+
const bytes = new TextEncoder().encode(JSON.stringify(initArgs));
|
|
6532
|
+
return domainHash(INIT_DOMAIN, [bytes]);
|
|
6533
|
+
}
|
|
6534
|
+
async function creationWarrantPreimage(p) {
|
|
6535
|
+
return domainHash(SIGN_DOMAIN3, [
|
|
6536
|
+
p.group,
|
|
6537
|
+
p.seed,
|
|
6538
|
+
p.authorAccount,
|
|
6539
|
+
p.deviceKey,
|
|
6540
|
+
p.executor,
|
|
6541
|
+
p.applicationId,
|
|
6542
|
+
new Uint8Array([p.serviceName === null ? 0 : 1]),
|
|
6543
|
+
p.serviceName ?? new Uint8Array(0),
|
|
6544
|
+
new Uint8Array([p.name === null ? 0 : 1]),
|
|
6545
|
+
p.name ?? new Uint8Array(0),
|
|
6546
|
+
p.initHash,
|
|
6547
|
+
u64le(p.accountHeads.length),
|
|
6548
|
+
...p.accountHeads,
|
|
6549
|
+
u64le(p.governanceFloor.length),
|
|
6550
|
+
...p.governanceFloor,
|
|
6551
|
+
u64le(p.nonce),
|
|
6552
|
+
u64le(p.notAfter)
|
|
6553
|
+
]);
|
|
6554
|
+
}
|
|
6555
|
+
async function signCreationWarrant(input) {
|
|
6556
|
+
const group = fromHex(input.group, "group", 32);
|
|
6557
|
+
const seed = input.seed === void 0 ? crypto.getRandomValues(new Uint8Array(32)) : fromHex(input.seed, "seed", 32);
|
|
6558
|
+
const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
|
|
6559
|
+
const executor = fromHex(input.executor, "executor", 32);
|
|
6560
|
+
const applicationId = fromHex(input.applicationId, "applicationId", 32);
|
|
6561
|
+
const serviceName = label(input.serviceName, "serviceName");
|
|
6562
|
+
const name = label(input.name, "name");
|
|
6563
|
+
const accountHeads = citedHeads(input.accountHeads, "accountHeads");
|
|
6564
|
+
const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
|
|
6565
|
+
const key = await importSigningKey(input.deviceSecret);
|
|
6566
|
+
const deviceKey = await derivePublicKey(input.deviceSecret);
|
|
6567
|
+
const initHash = await creationInitHash(input.initArgs);
|
|
6568
|
+
const preimage = await creationWarrantPreimage({
|
|
6569
|
+
group,
|
|
6570
|
+
seed,
|
|
6571
|
+
authorAccount,
|
|
6572
|
+
deviceKey,
|
|
6573
|
+
executor,
|
|
6574
|
+
applicationId,
|
|
6575
|
+
serviceName,
|
|
6576
|
+
name,
|
|
6577
|
+
initHash,
|
|
6578
|
+
accountHeads,
|
|
6579
|
+
governanceFloor,
|
|
6580
|
+
nonce: input.nonce,
|
|
6581
|
+
notAfter: input.notAfter
|
|
6582
|
+
});
|
|
6583
|
+
const signature = new Uint8Array(
|
|
6584
|
+
await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
|
|
6585
|
+
);
|
|
6586
|
+
const warrant = hex(
|
|
6587
|
+
concat(
|
|
6588
|
+
group,
|
|
6589
|
+
seed,
|
|
6590
|
+
authorAccount,
|
|
6591
|
+
deviceKey,
|
|
6592
|
+
executor,
|
|
6593
|
+
applicationId,
|
|
6594
|
+
borshOption(serviceName),
|
|
6595
|
+
borshOption(name),
|
|
6596
|
+
initHash,
|
|
6597
|
+
u32le(accountHeads.length),
|
|
6598
|
+
...accountHeads,
|
|
6599
|
+
u32le(governanceFloor.length),
|
|
6600
|
+
...governanceFloor,
|
|
6601
|
+
u64le(input.nonce),
|
|
6602
|
+
u64le(input.notAfter),
|
|
6603
|
+
signature
|
|
6604
|
+
)
|
|
6605
|
+
);
|
|
6606
|
+
return { warrant, seed: hex(seed) };
|
|
6607
|
+
}
|
|
6608
|
+
function parseCreationWarrant(warrant) {
|
|
6609
|
+
const clean = warrant.trim();
|
|
6610
|
+
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
6611
|
+
throw new Error("creation warrant must be an even number of hex characters");
|
|
6612
|
+
}
|
|
6613
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
6614
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
6615
|
+
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
6616
|
+
}
|
|
6617
|
+
const view = new DataView(bytes.buffer);
|
|
6618
|
+
let o = 0;
|
|
6619
|
+
const take = (n, what) => {
|
|
6620
|
+
if (o + n > bytes.length) {
|
|
6621
|
+
throw new Error(`creation warrant ends inside ${what} (${bytes.length} bytes)`);
|
|
6622
|
+
}
|
|
6623
|
+
const out = bytes.subarray(o, o + n);
|
|
6624
|
+
o += n;
|
|
6625
|
+
return out;
|
|
6626
|
+
};
|
|
6627
|
+
const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
|
|
6628
|
+
const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
|
|
6629
|
+
const option = (what) => {
|
|
6630
|
+
const tag2 = take(1, what)[0];
|
|
6631
|
+
if (tag2 === 0) return null;
|
|
6632
|
+
if (tag2 !== 1) throw new Error(`${what} has option tag ${tag2}, expected 0 or 1`);
|
|
6633
|
+
const len = u32(what);
|
|
6634
|
+
if (len > MAX_LABEL_BYTES) {
|
|
6635
|
+
throw new Error(`${what} is ${len} bytes, over the ${MAX_LABEL_BYTES} a node accepts`);
|
|
6636
|
+
}
|
|
6637
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(take(len, what));
|
|
6638
|
+
};
|
|
6639
|
+
const heads = (what) => {
|
|
6640
|
+
const count = u32(what);
|
|
6641
|
+
if (count > 64) {
|
|
6642
|
+
throw new Error(`${what} cites ${count} heads, over the 64 a node accepts`);
|
|
6643
|
+
}
|
|
6644
|
+
return Array.from({ length: count }, () => hex(take(32, what)));
|
|
6645
|
+
};
|
|
6646
|
+
const fields = {
|
|
6647
|
+
group: hex(take(32, "group")),
|
|
6648
|
+
seed: hex(take(32, "seed")),
|
|
6649
|
+
authorAccount: hex(take(32, "authorAccount")),
|
|
6650
|
+
deviceKey: hex(take(32, "deviceKey")),
|
|
6651
|
+
executor: hex(take(32, "executor")),
|
|
6652
|
+
applicationId: hex(take(32, "applicationId")),
|
|
6653
|
+
serviceName: option("serviceName"),
|
|
6654
|
+
name: option("name"),
|
|
6655
|
+
initHash: hex(take(32, "initHash")),
|
|
6656
|
+
accountHeads: heads("accountHeads"),
|
|
6657
|
+
governanceFloor: heads("governanceFloor"),
|
|
6658
|
+
nonce: u64("nonce"),
|
|
6659
|
+
notAfter: u64("notAfter"),
|
|
6660
|
+
signature: hex(take(64, "signature"))
|
|
6661
|
+
};
|
|
6662
|
+
if (o !== bytes.length) {
|
|
6663
|
+
throw new Error(
|
|
6664
|
+
`creation warrant is ${bytes.length} bytes but its fields account for ${o}`
|
|
6665
|
+
);
|
|
6666
|
+
}
|
|
6667
|
+
return fields;
|
|
6668
|
+
}
|
|
6669
|
+
function label(value, what) {
|
|
6670
|
+
if (value === void 0) return null;
|
|
6671
|
+
const bytes = new TextEncoder().encode(value);
|
|
6672
|
+
if (bytes.length > MAX_LABEL_BYTES) {
|
|
6673
|
+
throw new Error(
|
|
6674
|
+
`${what} is ${bytes.length} UTF-8 bytes, over the ${MAX_LABEL_BYTES} a node accepts`
|
|
6675
|
+
);
|
|
6676
|
+
}
|
|
6677
|
+
return bytes;
|
|
6678
|
+
}
|
|
6679
|
+
function borshOption(bytes) {
|
|
6680
|
+
if (bytes === null) return new Uint8Array([0]);
|
|
6681
|
+
return concat(new Uint8Array([1]), u32le(bytes.length), bytes);
|
|
6682
|
+
}
|
|
6683
|
+
|
|
6684
|
+
// src/warrant/governance-warrant.ts
|
|
6685
|
+
var SIGN_DOMAIN4 = new TextEncoder().encode("calimero.governance-warrant.v1");
|
|
6686
|
+
var OP_DOMAIN = new TextEncoder().encode("calimero.governance-warrant.op.v1");
|
|
6687
|
+
var KIND_BYTE = { group: 0, root: 1 };
|
|
6688
|
+
async function governanceOpHash(op) {
|
|
6689
|
+
return domainHash(OP_DOMAIN, [new Uint8Array([kindByte(op.kind)]), op.bytes]);
|
|
6690
|
+
}
|
|
6691
|
+
async function governanceWarrantPreimage(p) {
|
|
6692
|
+
return domainHash(SIGN_DOMAIN4, [
|
|
6693
|
+
p.scope,
|
|
6694
|
+
new Uint8Array([kindByte(p.kind)]),
|
|
6695
|
+
p.authorAccount,
|
|
6696
|
+
p.deviceKey,
|
|
6697
|
+
p.executor,
|
|
6698
|
+
p.opHash,
|
|
6699
|
+
u64le(p.accountHeads.length),
|
|
6700
|
+
...p.accountHeads,
|
|
6701
|
+
u64le(p.governanceFloor.length),
|
|
6702
|
+
...p.governanceFloor,
|
|
6703
|
+
u64le(p.nonce),
|
|
6704
|
+
u64le(p.notAfter)
|
|
6705
|
+
]);
|
|
6706
|
+
}
|
|
6707
|
+
async function signGovernanceWarrant(input) {
|
|
6708
|
+
const scope = fromHex(input.scope, "scope", 32);
|
|
6709
|
+
const authorAccount = fromHex(input.authorAccount, "authorAccount", 32);
|
|
6710
|
+
const executor = fromHex(input.executor, "executor", 32);
|
|
6711
|
+
const accountHeads = citedHeads(input.accountHeads, "accountHeads");
|
|
6712
|
+
const governanceFloor = citedHeads(input.governanceFloor, "governanceFloor");
|
|
6713
|
+
const kind = kindByte(input.op.kind);
|
|
6714
|
+
const key = await importSigningKey(input.deviceSecret);
|
|
6715
|
+
const deviceKey = await derivePublicKey(input.deviceSecret);
|
|
6716
|
+
const opHash = await governanceOpHash(input.op);
|
|
6717
|
+
const preimage = await governanceWarrantPreimage({
|
|
6718
|
+
scope,
|
|
6719
|
+
kind: input.op.kind,
|
|
6720
|
+
authorAccount,
|
|
6721
|
+
deviceKey,
|
|
6722
|
+
executor,
|
|
6723
|
+
opHash,
|
|
6724
|
+
accountHeads,
|
|
6725
|
+
governanceFloor,
|
|
6726
|
+
nonce: input.nonce,
|
|
6727
|
+
notAfter: input.notAfter
|
|
6728
|
+
});
|
|
6729
|
+
const signature = new Uint8Array(
|
|
6730
|
+
await crypto.subtle.sign({ name: "Ed25519" }, key, preimage)
|
|
6731
|
+
);
|
|
6732
|
+
return hex(
|
|
6733
|
+
concat(
|
|
6734
|
+
scope,
|
|
6735
|
+
new Uint8Array([kind]),
|
|
6736
|
+
authorAccount,
|
|
6737
|
+
deviceKey,
|
|
6738
|
+
executor,
|
|
6739
|
+
opHash,
|
|
6740
|
+
u32le(accountHeads.length),
|
|
6741
|
+
...accountHeads,
|
|
6742
|
+
u32le(governanceFloor.length),
|
|
6743
|
+
...governanceFloor,
|
|
6744
|
+
u64le(input.nonce),
|
|
6745
|
+
u64le(input.notAfter),
|
|
6746
|
+
signature
|
|
6747
|
+
)
|
|
6748
|
+
);
|
|
6749
|
+
}
|
|
6750
|
+
function parseGovernanceWarrant(warrant) {
|
|
6751
|
+
const clean = warrant.trim();
|
|
6752
|
+
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
6753
|
+
throw new Error("governance warrant must be an even number of hex characters");
|
|
6754
|
+
}
|
|
6755
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
6756
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
6757
|
+
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
6758
|
+
}
|
|
6759
|
+
const view = new DataView(bytes.buffer);
|
|
6760
|
+
let o = 0;
|
|
6761
|
+
const take = (n, what) => {
|
|
6762
|
+
if (o + n > bytes.length) {
|
|
6763
|
+
throw new Error(`governance warrant ends inside ${what} (${bytes.length} bytes)`);
|
|
6764
|
+
}
|
|
6765
|
+
const out = bytes.subarray(o, o + n);
|
|
6766
|
+
o += n;
|
|
6767
|
+
return out;
|
|
6768
|
+
};
|
|
6769
|
+
const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
|
|
6770
|
+
const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
|
|
6771
|
+
const heads = (what) => {
|
|
6772
|
+
const count = u32(what);
|
|
6773
|
+
if (count > 64) {
|
|
6774
|
+
throw new Error(`${what} cites ${count} heads, over the 64 a node accepts`);
|
|
6775
|
+
}
|
|
6776
|
+
return Array.from({ length: count }, () => hex(take(32, what)));
|
|
6777
|
+
};
|
|
6778
|
+
const kind = () => {
|
|
6779
|
+
const byte = take(1, "kind")[0];
|
|
6780
|
+
if (byte === 0) return "group";
|
|
6781
|
+
if (byte === 1) return "root";
|
|
6782
|
+
throw new Error(`governance warrant has kind ${byte}, expected 0 (group) or 1 (root)`);
|
|
6783
|
+
};
|
|
6784
|
+
const fields = {
|
|
6785
|
+
scope: hex(take(32, "scope")),
|
|
6786
|
+
kind: kind(),
|
|
6787
|
+
authorAccount: hex(take(32, "authorAccount")),
|
|
6788
|
+
deviceKey: hex(take(32, "deviceKey")),
|
|
6789
|
+
executor: hex(take(32, "executor")),
|
|
6790
|
+
opHash: hex(take(32, "opHash")),
|
|
6791
|
+
accountHeads: heads("accountHeads"),
|
|
6792
|
+
governanceFloor: heads("governanceFloor"),
|
|
6793
|
+
nonce: u64("nonce"),
|
|
6794
|
+
notAfter: u64("notAfter"),
|
|
6795
|
+
signature: hex(take(64, "signature"))
|
|
6796
|
+
};
|
|
6797
|
+
if (o !== bytes.length) {
|
|
6798
|
+
throw new Error(
|
|
6799
|
+
`governance warrant is ${bytes.length} bytes but its fields account for ${o}`
|
|
6800
|
+
);
|
|
6801
|
+
}
|
|
6802
|
+
return fields;
|
|
6803
|
+
}
|
|
6804
|
+
function kindByte(kind) {
|
|
6805
|
+
const byte = KIND_BYTE[kind];
|
|
6806
|
+
if (byte === void 0) {
|
|
6807
|
+
throw new Error(`op kind must be 'group' or 'root', got ${String(kind)}`);
|
|
6808
|
+
}
|
|
6809
|
+
return byte;
|
|
6524
6810
|
}
|
|
6525
6811
|
|
|
6526
6812
|
// src/relay/relay-client.ts
|
|
@@ -6583,6 +6869,156 @@ var RelayClient = class {
|
|
|
6583
6869
|
);
|
|
6584
6870
|
return { rootHash: body.data.rootHash, returns: body.data.returns ?? null };
|
|
6585
6871
|
}
|
|
6872
|
+
/**
|
|
6873
|
+
* Ask the relay what it can do about creating contexts in `groupId`.
|
|
6874
|
+
*
|
|
6875
|
+
* Pass `author` (an account, hex) to also learn whether that account may
|
|
6876
|
+
* create here — `authorMayCreate` is present only then.
|
|
6877
|
+
*/
|
|
6878
|
+
async describeCreation(groupId, opts = {}) {
|
|
6879
|
+
const query = opts.author ? `?author=${encodeURIComponent(opts.author)}` : "";
|
|
6880
|
+
const body = await this.json(
|
|
6881
|
+
"GET",
|
|
6882
|
+
`/admin-api/groups/${encodeURIComponent(groupId)}/context-intents${query}`
|
|
6883
|
+
);
|
|
6884
|
+
return body.data;
|
|
6885
|
+
}
|
|
6886
|
+
/**
|
|
6887
|
+
* Create a context in a group through the relay, as the author.
|
|
6888
|
+
*
|
|
6889
|
+
* Always asks {@link describeCreation} first — with the author, so both
|
|
6890
|
+
* standing checks happen before anything is signed. A relay without standing
|
|
6891
|
+
* in the group, or an author without `CAN_CREATE_CONTEXT`, is refused here as
|
|
6892
|
+
* an {@link IntentRefusedError} *before* a nonce is taken: the nonce source
|
|
6893
|
+
* is monotonic, and a warrant nobody will spend burns one for nothing.
|
|
6894
|
+
*
|
|
6895
|
+
* The creation warrant's nonce is spent in the new context's per-device
|
|
6896
|
+
* ledger — the same one later `execute` calls in that context draw from — so
|
|
6897
|
+
* it comes from the configured {@link NonceSource} like any other warrant.
|
|
6898
|
+
*
|
|
6899
|
+
* Sealing: nothing here is plaintext-specific. The request goes through the
|
|
6900
|
+
* injected `fetch`, so a `createAttestedSealedFetch` seals it exactly as it
|
|
6901
|
+
* seals `execute`. Whether the relay *accepts* it sealed is the node's call:
|
|
6902
|
+
* a proxy-auth relay only admits sealed requests for routes `merod` serves
|
|
6903
|
+
* without a credential, so `/admin-api/groups/:id/context-intents` must be on
|
|
6904
|
+
* that list on the core side or the request comes back
|
|
6905
|
+
* `403 sealed_route_unguarded`.
|
|
6906
|
+
*/
|
|
6907
|
+
async createContext(input) {
|
|
6908
|
+
const described = await this.describeCreation(input.groupId, {
|
|
6909
|
+
author: this.config.authorAccount
|
|
6910
|
+
});
|
|
6911
|
+
if (!described.canCreateOnBehalf) {
|
|
6912
|
+
throw new IntentRefusedError(
|
|
6913
|
+
`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)`,
|
|
6914
|
+
false,
|
|
6915
|
+
403
|
|
6916
|
+
);
|
|
6917
|
+
}
|
|
6918
|
+
if (described.authorMayCreate === false) {
|
|
6919
|
+
throw new IntentRefusedError(
|
|
6920
|
+
`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)`,
|
|
6921
|
+
false,
|
|
6922
|
+
403
|
|
6923
|
+
);
|
|
6924
|
+
}
|
|
6925
|
+
const executor = this.checkedExecutor(described.executorAccount);
|
|
6926
|
+
const initArgs = input.initArgs ?? {};
|
|
6927
|
+
const nonce2 = await this.config.nonces.next();
|
|
6928
|
+
const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
6929
|
+
const { warrant } = await signCreationWarrant({
|
|
6930
|
+
group: input.groupId,
|
|
6931
|
+
seed: input.seed,
|
|
6932
|
+
authorAccount: this.config.authorAccount,
|
|
6933
|
+
executor,
|
|
6934
|
+
applicationId: input.applicationId,
|
|
6935
|
+
serviceName: input.serviceName,
|
|
6936
|
+
name: input.name,
|
|
6937
|
+
initArgs,
|
|
6938
|
+
nonce: nonce2,
|
|
6939
|
+
notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
|
|
6940
|
+
deviceSecret: this.config.deviceSecret
|
|
6941
|
+
});
|
|
6942
|
+
const body = await this.json(
|
|
6943
|
+
"POST",
|
|
6944
|
+
`/admin-api/groups/${encodeURIComponent(input.groupId)}/context-intents`,
|
|
6945
|
+
{ warrant, authorProof: this.config.authorProof, initArgs }
|
|
6946
|
+
);
|
|
6947
|
+
return {
|
|
6948
|
+
contextId: body.data.contextId,
|
|
6949
|
+
groupId: body.data.groupId,
|
|
6950
|
+
memberPublicKey: body.data.memberPublicKey
|
|
6951
|
+
};
|
|
6952
|
+
}
|
|
6953
|
+
/**
|
|
6954
|
+
* Ask the relay whether it may publish governance ops for members of
|
|
6955
|
+
* `groupId`, and which account a warrant must name.
|
|
6956
|
+
*/
|
|
6957
|
+
async describeGovernance(groupId) {
|
|
6958
|
+
const body = await this.json(
|
|
6959
|
+
"GET",
|
|
6960
|
+
`/admin-api/groups/${encodeURIComponent(groupId)}/governance-intents`
|
|
6961
|
+
);
|
|
6962
|
+
return body.data;
|
|
6963
|
+
}
|
|
6964
|
+
/**
|
|
6965
|
+
* Publish one governance op through the relay, as the author.
|
|
6966
|
+
*
|
|
6967
|
+
* The warrant commits to the op's exact bytes and is scoped to `groupId`, so
|
|
6968
|
+
* the relay can neither change what the op does nor spend it elsewhere. Every
|
|
6969
|
+
* peer applies the op as the author, so the author's own rights decide
|
|
6970
|
+
* (`MANAGE_MEMBERS` to add a member, `CAN_CREATE_SUBGROUP` to create a
|
|
6971
|
+
* subgroup, and so on); a lack of them comes back as an
|
|
6972
|
+
* {@link IntentRefusedError} from the relay.
|
|
6973
|
+
*
|
|
6974
|
+
* Always asks {@link describeGovernance} first and refuses *before* taking a
|
|
6975
|
+
* nonce when the relay has no standing, for the reason `createContext` does.
|
|
6976
|
+
* The nonce comes from the configured {@link NonceSource}; it is spent in a
|
|
6977
|
+
* per-group ledger, and a monotonic source is correct for it as for every
|
|
6978
|
+
* other warrant.
|
|
6979
|
+
*/
|
|
6980
|
+
async govern(input) {
|
|
6981
|
+
const described = await this.describeGovernance(input.groupId);
|
|
6982
|
+
if (!described.canActOnBehalf) {
|
|
6983
|
+
throw new IntentRefusedError(
|
|
6984
|
+
`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)`,
|
|
6985
|
+
false,
|
|
6986
|
+
403
|
|
6987
|
+
);
|
|
6988
|
+
}
|
|
6989
|
+
const executor = this.checkedExecutor(described.executorAccount);
|
|
6990
|
+
const nonce2 = await this.config.nonces.next();
|
|
6991
|
+
const ttl = this.config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
6992
|
+
const warrant = await signGovernanceWarrant({
|
|
6993
|
+
scope: input.groupId,
|
|
6994
|
+
op: input.op,
|
|
6995
|
+
authorAccount: this.config.authorAccount,
|
|
6996
|
+
executor,
|
|
6997
|
+
nonce: nonce2,
|
|
6998
|
+
notAfter: BigInt(Math.floor(Date.now() / 1e3)) + BigInt(ttl),
|
|
6999
|
+
deviceSecret: this.config.deviceSecret
|
|
7000
|
+
});
|
|
7001
|
+
const body = await this.json(
|
|
7002
|
+
"POST",
|
|
7003
|
+
`/admin-api/groups/${encodeURIComponent(input.groupId)}/governance-intents`,
|
|
7004
|
+
{ warrant, authorProof: this.config.authorProof, op: hex(input.op.bytes) }
|
|
7005
|
+
);
|
|
7006
|
+
return { groupId: body.data.groupId };
|
|
7007
|
+
}
|
|
7008
|
+
/**
|
|
7009
|
+
* The executor a warrant must name: the one the relay answered with, which a
|
|
7010
|
+
* configured account must agree with. Remembered for later calls.
|
|
7011
|
+
*/
|
|
7012
|
+
checkedExecutor(answered) {
|
|
7013
|
+
const configured = this.config.executorAccount;
|
|
7014
|
+
if (configured && configured.toLowerCase() !== answered.toLowerCase()) {
|
|
7015
|
+
throw new Error(
|
|
7016
|
+
`configured executorAccount ${configured} is not the relay's account ${answered}; a warrant naming it would be unspendable`
|
|
7017
|
+
);
|
|
7018
|
+
}
|
|
7019
|
+
this.executorAccount = answered;
|
|
7020
|
+
return answered;
|
|
7021
|
+
}
|
|
6586
7022
|
async json(method, path, body) {
|
|
6587
7023
|
const headers = { Accept: "application/json" };
|
|
6588
7024
|
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
@@ -7282,8 +7718,8 @@ var REGISTERS = ["mrtd", "rtmr0", "rtmr1", "rtmr2", "rtmr3"];
|
|
|
7282
7718
|
function createQuoteVerifier(options) {
|
|
7283
7719
|
const allowedMrtd = measurements(options.allowedMrtd ?? [], "allowedMrtd");
|
|
7284
7720
|
const allowedImages = (options.allowedMeasurements ?? []).map((image, index) => {
|
|
7285
|
-
const
|
|
7286
|
-
return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${
|
|
7721
|
+
const label2 = `allowedMeasurements[${index}]`;
|
|
7722
|
+
return REGISTERS.map((register) => measurements([image?.[register] ?? ""], `${label2}.${register}`)[0]).join();
|
|
7287
7723
|
});
|
|
7288
7724
|
const allowedRtmr = [
|
|
7289
7725
|
measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
|
|
@@ -7352,11 +7788,11 @@ function createQuoteVerifier(options) {
|
|
|
7352
7788
|
};
|
|
7353
7789
|
return Object.assign(verifier, { includeCollateral: true });
|
|
7354
7790
|
}
|
|
7355
|
-
function measurements(values,
|
|
7791
|
+
function measurements(values, label2) {
|
|
7356
7792
|
return values.map((value) => {
|
|
7357
7793
|
const normalized = value.trim().toLowerCase();
|
|
7358
7794
|
if (!MEASUREMENT_HEX.test(normalized)) {
|
|
7359
|
-
throw new Error(`${
|
|
7795
|
+
throw new Error(`${label2} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
|
|
7360
7796
|
}
|
|
7361
7797
|
return normalized;
|
|
7362
7798
|
});
|
|
@@ -7469,8 +7905,8 @@ var SIGNATURE_HASHES = {
|
|
|
7469
7905
|
};
|
|
7470
7906
|
function read(bytes, offset, limit) {
|
|
7471
7907
|
if (offset + 2 > limit) throw new Error("it is truncated");
|
|
7472
|
-
const
|
|
7473
|
-
if ((
|
|
7908
|
+
const tag2 = bytes[offset];
|
|
7909
|
+
if ((tag2 & 31) === 31) throw new Error("it uses a tag this parser does not read");
|
|
7474
7910
|
let length = bytes[offset + 1];
|
|
7475
7911
|
let start = offset + 2;
|
|
7476
7912
|
if (length & 128) {
|
|
@@ -7482,7 +7918,7 @@ function read(bytes, offset, limit) {
|
|
|
7482
7918
|
}
|
|
7483
7919
|
const end = start + length;
|
|
7484
7920
|
if (end > limit) throw new Error("it is truncated");
|
|
7485
|
-
return { tag, bytes, offset, start, end };
|
|
7921
|
+
return { tag: tag2, bytes, offset, start, end };
|
|
7486
7922
|
}
|
|
7487
7923
|
function parseDer(bytes) {
|
|
7488
7924
|
const element = read(bytes, 0, bytes.length);
|
|
@@ -7498,8 +7934,8 @@ function children(element) {
|
|
|
7498
7934
|
}
|
|
7499
7935
|
return out;
|
|
7500
7936
|
}
|
|
7501
|
-
function expectTag(element,
|
|
7502
|
-
if (!element || element.tag !==
|
|
7937
|
+
function expectTag(element, tag2, what) {
|
|
7938
|
+
if (!element || element.tag !== tag2) throw new Error(`${what} is not where it should be`);
|
|
7503
7939
|
return element;
|
|
7504
7940
|
}
|
|
7505
7941
|
function contents(element) {
|
|
@@ -7961,11 +8397,103 @@ function splitVersion2(version) {
|
|
|
7961
8397
|
return [[Number(match[1]), Number(match[2]), Number(match[3])], match[4]];
|
|
7962
8398
|
}
|
|
7963
8399
|
|
|
8400
|
+
// src/warrant/governance-op.ts
|
|
8401
|
+
var ROLE = {
|
|
8402
|
+
Admin: 0,
|
|
8403
|
+
Member: 1,
|
|
8404
|
+
ReadOnly: 2
|
|
8405
|
+
};
|
|
8406
|
+
var GROUP_OP = {
|
|
8407
|
+
MemberAdded: 1,
|
|
8408
|
+
MemberRemoved: 2,
|
|
8409
|
+
MemberLeft: 3,
|
|
8410
|
+
MemberRoleSet: 4
|
|
8411
|
+
};
|
|
8412
|
+
var ROOT_OP = {
|
|
8413
|
+
GroupCreated: 0,
|
|
8414
|
+
GroupReparented: 1,
|
|
8415
|
+
GroupDeleted: 2
|
|
8416
|
+
};
|
|
8417
|
+
var CLEARED_HASH = new Uint8Array(32);
|
|
8418
|
+
var EMPTY_VEC = u32le(0);
|
|
8419
|
+
function role(value) {
|
|
8420
|
+
const byte = ROLE[value];
|
|
8421
|
+
if (byte === void 0) {
|
|
8422
|
+
throw new Error(`role must be Admin, Member or ReadOnly, got ${String(value)}`);
|
|
8423
|
+
}
|
|
8424
|
+
return new Uint8Array([byte]);
|
|
8425
|
+
}
|
|
8426
|
+
function tag(byte) {
|
|
8427
|
+
return new Uint8Array([byte]);
|
|
8428
|
+
}
|
|
8429
|
+
function memberAddedOp(member, memberRole) {
|
|
8430
|
+
return {
|
|
8431
|
+
kind: "group",
|
|
8432
|
+
bytes: concat(tag(GROUP_OP.MemberAdded), fromHex(member, "member", 32), role(memberRole))
|
|
8433
|
+
};
|
|
8434
|
+
}
|
|
8435
|
+
function memberRemovedOp(member) {
|
|
8436
|
+
return {
|
|
8437
|
+
kind: "group",
|
|
8438
|
+
bytes: concat(
|
|
8439
|
+
tag(GROUP_OP.MemberRemoved),
|
|
8440
|
+
fromHex(member, "member", 32),
|
|
8441
|
+
CLEARED_HASH,
|
|
8442
|
+
EMPTY_VEC
|
|
8443
|
+
)
|
|
8444
|
+
};
|
|
8445
|
+
}
|
|
8446
|
+
function memberLeftOp(member) {
|
|
8447
|
+
return {
|
|
8448
|
+
kind: "group",
|
|
8449
|
+
bytes: concat(
|
|
8450
|
+
tag(GROUP_OP.MemberLeft),
|
|
8451
|
+
fromHex(member, "member", 32),
|
|
8452
|
+
CLEARED_HASH,
|
|
8453
|
+
EMPTY_VEC
|
|
8454
|
+
)
|
|
8455
|
+
};
|
|
8456
|
+
}
|
|
8457
|
+
function memberRoleSetOp(member, memberRole) {
|
|
8458
|
+
return {
|
|
8459
|
+
kind: "group",
|
|
8460
|
+
bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, "member", 32), role(memberRole))
|
|
8461
|
+
};
|
|
8462
|
+
}
|
|
8463
|
+
function groupCreatedOp(input) {
|
|
8464
|
+
return {
|
|
8465
|
+
kind: "root",
|
|
8466
|
+
bytes: concat(
|
|
8467
|
+
tag(ROOT_OP.GroupCreated),
|
|
8468
|
+
fromHex(input.groupId, "groupId", 32),
|
|
8469
|
+
fromHex(input.parentId, "parentId", 32),
|
|
8470
|
+
tag(input.restricted ? 1 : 0),
|
|
8471
|
+
fromHex(input.admin, "admin", 32)
|
|
8472
|
+
)
|
|
8473
|
+
};
|
|
8474
|
+
}
|
|
8475
|
+
function groupReparentedOp(childGroupId, newParentId) {
|
|
8476
|
+
return {
|
|
8477
|
+
kind: "root",
|
|
8478
|
+
bytes: concat(
|
|
8479
|
+
tag(ROOT_OP.GroupReparented),
|
|
8480
|
+
fromHex(childGroupId, "childGroupId", 32),
|
|
8481
|
+
fromHex(newParentId, "newParentId", 32)
|
|
8482
|
+
)
|
|
8483
|
+
};
|
|
8484
|
+
}
|
|
8485
|
+
function groupDeletedOp(rootGroupId) {
|
|
8486
|
+
return {
|
|
8487
|
+
kind: "root",
|
|
8488
|
+
bytes: concat(tag(ROOT_OP.GroupDeleted), fromHex(rootGroupId, "rootGroupId", 32), EMPTY_VEC, EMPTY_VEC)
|
|
8489
|
+
};
|
|
8490
|
+
}
|
|
8491
|
+
|
|
7964
8492
|
// src/namespace-op/namespace-op.ts
|
|
7965
|
-
var SIGNED_NAMESPACE_OP_SCHEMA_VERSION =
|
|
8493
|
+
var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 15;
|
|
7966
8494
|
var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
|
|
7967
8495
|
var NAMESPACE_OP_ROOT = 0;
|
|
7968
|
-
var
|
|
8496
|
+
var ROOT_OP2 = {
|
|
7969
8497
|
GroupCreated: 0,
|
|
7970
8498
|
GroupReparented: 1,
|
|
7971
8499
|
GroupDeleted: 2,
|
|
@@ -7978,18 +8506,18 @@ var ROOT_OP = {
|
|
|
7978
8506
|
NamespaceCreatedV2: 9,
|
|
7979
8507
|
MemberJoinedViaTeeAttestation: 10
|
|
7980
8508
|
};
|
|
7981
|
-
function bytes32(value,
|
|
8509
|
+
function bytes32(value, label2) {
|
|
7982
8510
|
if (value.length !== 32) {
|
|
7983
|
-
throw new Error(`${
|
|
8511
|
+
throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
|
|
7984
8512
|
}
|
|
7985
8513
|
return new Uint8Array(value);
|
|
7986
8514
|
}
|
|
7987
|
-
function optionalBytes32(value,
|
|
8515
|
+
function optionalBytes32(value, label2) {
|
|
7988
8516
|
if (value === null || value === void 0) {
|
|
7989
8517
|
return new Uint8Array([0]);
|
|
7990
8518
|
}
|
|
7991
8519
|
if (value.length !== 32) {
|
|
7992
|
-
throw new Error(`${
|
|
8520
|
+
throw new Error(`${label2} must be 32 bytes, got ${value.length}`);
|
|
7993
8521
|
}
|
|
7994
8522
|
return concat(new Uint8Array([1]), new Uint8Array(value));
|
|
7995
8523
|
}
|
|
@@ -8040,13 +8568,13 @@ async function signMemberJoinOp(input) {
|
|
|
8040
8568
|
const expires = BigInt(input.invitation.invitation.expiration_timestamp ?? 0) !== 0n;
|
|
8041
8569
|
const joinedAt = input.joinedAt ?? BigInt(Math.floor(Date.now() / 1e3));
|
|
8042
8570
|
const rootOp = expires ? concat(
|
|
8043
|
-
new Uint8Array([
|
|
8571
|
+
new Uint8Array([ROOT_OP2.MemberJoinedAt]),
|
|
8044
8572
|
fromHex(input.member, "member", 32),
|
|
8045
8573
|
encodeSignedInvitation(input.invitation),
|
|
8046
8574
|
u64le(joinedAt),
|
|
8047
8575
|
credential
|
|
8048
8576
|
) : concat(
|
|
8049
|
-
new Uint8Array([
|
|
8577
|
+
new Uint8Array([ROOT_OP2.MemberJoined]),
|
|
8050
8578
|
fromHex(input.member, "member", 32),
|
|
8051
8579
|
encodeSignedInvitation(input.invitation),
|
|
8052
8580
|
credential
|
|
@@ -8071,7 +8599,7 @@ async function signMemberJoinOp(input) {
|
|
|
8071
8599
|
}
|
|
8072
8600
|
|
|
8073
8601
|
// src/login/login.ts
|
|
8074
|
-
var
|
|
8602
|
+
var SIGN_DOMAIN5 = new TextEncoder().encode("calimero.auth.login.v1");
|
|
8075
8603
|
var AUDIENCE_TAG = {
|
|
8076
8604
|
webOrigin: 0,
|
|
8077
8605
|
codeSigningId: 1,
|
|
@@ -8095,12 +8623,12 @@ function audienceSigningBytes(audience) {
|
|
|
8095
8623
|
);
|
|
8096
8624
|
}
|
|
8097
8625
|
function audienceWire(audience) {
|
|
8098
|
-
const
|
|
8626
|
+
const tag2 = Uint8Array.from([AUDIENCE_TAG[audience.kind]]);
|
|
8099
8627
|
if (audience.kind === "cli") {
|
|
8100
|
-
return
|
|
8628
|
+
return tag2;
|
|
8101
8629
|
}
|
|
8102
8630
|
const body = audienceBody(audience);
|
|
8103
|
-
return concat(
|
|
8631
|
+
return concat(tag2, u32le(body.length), body);
|
|
8104
8632
|
}
|
|
8105
8633
|
async function signLoginStatement(input) {
|
|
8106
8634
|
const node = fromHex(input.node, "node", 32);
|
|
@@ -8110,7 +8638,7 @@ async function signLoginStatement(input) {
|
|
|
8110
8638
|
const deviceKey = await derivePublicKey(input.deviceSecret);
|
|
8111
8639
|
const issuedAt = u64le(input.issuedAt);
|
|
8112
8640
|
const expiresAt = u64le(input.expiresAt);
|
|
8113
|
-
const preimage = await domainHash(
|
|
8641
|
+
const preimage = await domainHash(SIGN_DOMAIN5, [
|
|
8114
8642
|
node,
|
|
8115
8643
|
audienceSigningBytes(input.audience),
|
|
8116
8644
|
challenge,
|
|
@@ -8345,6 +8873,7 @@ export {
|
|
|
8345
8873
|
createSignedReleaseVerifier,
|
|
8346
8874
|
createTimeoutSignal,
|
|
8347
8875
|
createUniversalHttpClient,
|
|
8876
|
+
creationInitHash,
|
|
8348
8877
|
deviceCertPayload,
|
|
8349
8878
|
discoverLocalNodes,
|
|
8350
8879
|
encodeSignedInvitation,
|
|
@@ -8353,6 +8882,10 @@ export {
|
|
|
8353
8882
|
fetchNodeReleaseVersion,
|
|
8354
8883
|
generateAccountRoot,
|
|
8355
8884
|
generateSessionKey,
|
|
8885
|
+
governanceOpHash,
|
|
8886
|
+
groupCreatedOp,
|
|
8887
|
+
groupDeletedOp,
|
|
8888
|
+
groupReparentedOp,
|
|
8356
8889
|
hasCap,
|
|
8357
8890
|
hexEncodeUtf8,
|
|
8358
8891
|
intentHash,
|
|
@@ -8360,10 +8893,16 @@ export {
|
|
|
8360
8893
|
jsonCodec,
|
|
8361
8894
|
localNodeUrl,
|
|
8362
8895
|
login,
|
|
8896
|
+
memberAddedOp,
|
|
8897
|
+
memberLeftOp,
|
|
8898
|
+
memberRemovedOp,
|
|
8899
|
+
memberRoleSetOp,
|
|
8363
8900
|
mintDeviceId,
|
|
8364
8901
|
nodeEndpoint,
|
|
8365
8902
|
nodeReleaseVersionFromImage,
|
|
8366
8903
|
parseAuthCallback,
|
|
8904
|
+
parseCreationWarrant,
|
|
8905
|
+
parseGovernanceWarrant,
|
|
8367
8906
|
probeNodeHealth,
|
|
8368
8907
|
redeemInvitation,
|
|
8369
8908
|
requestBodyHash,
|
|
@@ -8371,7 +8910,9 @@ export {
|
|
|
8371
8910
|
shouldRetain,
|
|
8372
8911
|
signAccountLink,
|
|
8373
8912
|
signAccountLogin,
|
|
8913
|
+
signCreationWarrant,
|
|
8374
8914
|
signDeviceCert,
|
|
8915
|
+
signGovernanceWarrant,
|
|
8375
8916
|
signLoginStatement,
|
|
8376
8917
|
signMemberJoinOp,
|
|
8377
8918
|
signRequest,
|