@calimero-network/mero-js 22.4.0 → 22.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +317 -25
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +317 -25
- 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 +56 -0
- package/dist/relay/relay-client.js +62 -7
- 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 +4 -0
- package/dist/warrant/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mint a governance warrant: the author's consent for one relay to publish one
|
|
3
|
+
* governance op in the author's name, once.
|
|
4
|
+
*
|
|
5
|
+
* The intent warrant lets an account with no node write to a context and the
|
|
6
|
+
* creation warrant lets it create one. This covers the governance around them:
|
|
7
|
+
* adding the other person to a DM, creating a channel's subgroup, changing a
|
|
8
|
+
* role. The relay wraps the op in `OnBehalf` and publishes it; every peer applies
|
|
9
|
+
* the inner op **as the author**, so the author's own authority decides
|
|
10
|
+
* (`MANAGE_MEMBERS` to add a member, `CAN_CREATE_SUBGROUP` to create a subgroup,
|
|
11
|
+
* and so on) and the relay needs only standing to act for members.
|
|
12
|
+
*
|
|
13
|
+
* **It commits to the op's bytes.** The warrant carries
|
|
14
|
+
* `H(kind || op bytes)` over the op's delegable form (see `governance-op.ts`), so
|
|
15
|
+
* what the op does, to whom and where is signed byte for byte and the relay
|
|
16
|
+
* chooses nothing about it. `kind` is inside the commitment, so a group op can
|
|
17
|
+
* never be presented as a root op whose bytes happen to coincide.
|
|
18
|
+
*
|
|
19
|
+
* **`scope` is where the op is published**: the group itself for a group op,
|
|
20
|
+
* the namespace for a root op.
|
|
21
|
+
*
|
|
22
|
+
* Same primitives and the same two-widths trap as `warrant.ts`: counts inside
|
|
23
|
+
* the encoding are borsh `u32`, counts in the signing preimage are `u64`.
|
|
24
|
+
*
|
|
25
|
+
* **The byte contract is pinned in core**, at
|
|
26
|
+
* `crates/account/src/tests/governance_wire_fixture.rs`, and this module's test
|
|
27
|
+
* asserts the same vectors. A drift would surface at a relay as a 400.
|
|
28
|
+
*/
|
|
29
|
+
import { concat, derivePublicKey, domainHash, fromHex, hex, importSigningKey, u32le, u64le, } from '../crypto/internal.js';
|
|
30
|
+
import { citedHeads } from './warrant.js';
|
|
31
|
+
const SIGN_DOMAIN = new TextEncoder().encode('calimero.governance-warrant.v1');
|
|
32
|
+
const OP_DOMAIN = new TextEncoder().encode('calimero.governance-warrant.op.v1');
|
|
33
|
+
/** core's `GovernanceOpKind` discriminants. */
|
|
34
|
+
const KIND_BYTE = { group: 0, root: 1 };
|
|
35
|
+
/**
|
|
36
|
+
* The commitment a governance warrant carries in place of the op:
|
|
37
|
+
* `H(kind || op bytes)` under its own domain.
|
|
38
|
+
*/
|
|
39
|
+
export async function governanceOpHash(op) {
|
|
40
|
+
return domainHash(OP_DOMAIN, [new Uint8Array([kindByte(op.kind)]), op.bytes]);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The bytes the device key signs. Exported for the conformance test only, not
|
|
44
|
+
* from the package root.
|
|
45
|
+
*/
|
|
46
|
+
export async function governanceWarrantPreimage(p) {
|
|
47
|
+
// u64 counts HERE; u32 on the wire. The counts keep the two adjacent head
|
|
48
|
+
// lists from being relabelled into each other.
|
|
49
|
+
return domainHash(SIGN_DOMAIN, [
|
|
50
|
+
p.scope,
|
|
51
|
+
new Uint8Array([kindByte(p.kind)]),
|
|
52
|
+
p.authorAccount,
|
|
53
|
+
p.deviceKey,
|
|
54
|
+
p.executor,
|
|
55
|
+
p.opHash,
|
|
56
|
+
u64le(p.accountHeads.length),
|
|
57
|
+
...p.accountHeads,
|
|
58
|
+
u64le(p.governanceFloor.length),
|
|
59
|
+
...p.governanceFloor,
|
|
60
|
+
u64le(p.nonce),
|
|
61
|
+
u64le(p.notAfter),
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Sign a governance warrant and return it hex-encoded (borsh
|
|
66
|
+
* `GovernanceWarrant`), ready for the relay alongside the op's own bytes.
|
|
67
|
+
*/
|
|
68
|
+
export async function signGovernanceWarrant(input) {
|
|
69
|
+
const scope = fromHex(input.scope, 'scope', 32);
|
|
70
|
+
const authorAccount = fromHex(input.authorAccount, 'authorAccount', 32);
|
|
71
|
+
const executor = fromHex(input.executor, 'executor', 32);
|
|
72
|
+
const accountHeads = citedHeads(input.accountHeads, 'accountHeads');
|
|
73
|
+
const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');
|
|
74
|
+
const kind = kindByte(input.op.kind);
|
|
75
|
+
const key = await importSigningKey(input.deviceSecret);
|
|
76
|
+
const deviceKey = await derivePublicKey(input.deviceSecret);
|
|
77
|
+
const opHash = await governanceOpHash(input.op);
|
|
78
|
+
const preimage = await governanceWarrantPreimage({
|
|
79
|
+
scope,
|
|
80
|
+
kind: input.op.kind,
|
|
81
|
+
authorAccount,
|
|
82
|
+
deviceKey,
|
|
83
|
+
executor,
|
|
84
|
+
opHash,
|
|
85
|
+
accountHeads,
|
|
86
|
+
governanceFloor,
|
|
87
|
+
nonce: input.nonce,
|
|
88
|
+
notAfter: input.notAfter,
|
|
89
|
+
});
|
|
90
|
+
const signature = new Uint8Array(await crypto.subtle.sign({ name: 'Ed25519' }, key, preimage));
|
|
91
|
+
return hex(concat(scope, new Uint8Array([kind]), authorAccount, deviceKey, executor, opHash, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, u64le(input.nonce), u64le(input.notAfter), signature));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Decode a hex governance warrant back into its fields.
|
|
95
|
+
*
|
|
96
|
+
* Refuses truncated or trailing bytes and an unknown kind. Does not verify the
|
|
97
|
+
* signature.
|
|
98
|
+
*/
|
|
99
|
+
export function parseGovernanceWarrant(warrant) {
|
|
100
|
+
const clean = warrant.trim();
|
|
101
|
+
if (!/^[0-9a-fA-F]*$/.test(clean) || clean.length % 2 !== 0) {
|
|
102
|
+
throw new Error('governance warrant must be an even number of hex characters');
|
|
103
|
+
}
|
|
104
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
105
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
106
|
+
bytes[i] = Number.parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
107
|
+
}
|
|
108
|
+
const view = new DataView(bytes.buffer);
|
|
109
|
+
let o = 0;
|
|
110
|
+
const take = (n, what) => {
|
|
111
|
+
if (o + n > bytes.length) {
|
|
112
|
+
throw new Error(`governance warrant ends inside ${what} (${bytes.length} bytes)`);
|
|
113
|
+
}
|
|
114
|
+
const out = bytes.subarray(o, o + n);
|
|
115
|
+
o += n;
|
|
116
|
+
return out;
|
|
117
|
+
};
|
|
118
|
+
const u32 = (what) => view.getUint32(take(4, what).byteOffset, true);
|
|
119
|
+
const u64 = (what) => view.getBigUint64(take(8, what).byteOffset, true);
|
|
120
|
+
const heads = (what) => {
|
|
121
|
+
const count = u32(what);
|
|
122
|
+
if (count > 64) {
|
|
123
|
+
throw new Error(`${what} cites ${count} heads, over the 64 a node accepts`);
|
|
124
|
+
}
|
|
125
|
+
return Array.from({ length: count }, () => hex(take(32, what)));
|
|
126
|
+
};
|
|
127
|
+
const kind = () => {
|
|
128
|
+
const byte = take(1, 'kind')[0];
|
|
129
|
+
if (byte === 0)
|
|
130
|
+
return 'group';
|
|
131
|
+
if (byte === 1)
|
|
132
|
+
return 'root';
|
|
133
|
+
throw new Error(`governance warrant has kind ${byte}, expected 0 (group) or 1 (root)`);
|
|
134
|
+
};
|
|
135
|
+
const fields = {
|
|
136
|
+
scope: hex(take(32, 'scope')),
|
|
137
|
+
kind: kind(),
|
|
138
|
+
authorAccount: hex(take(32, 'authorAccount')),
|
|
139
|
+
deviceKey: hex(take(32, 'deviceKey')),
|
|
140
|
+
executor: hex(take(32, 'executor')),
|
|
141
|
+
opHash: hex(take(32, 'opHash')),
|
|
142
|
+
accountHeads: heads('accountHeads'),
|
|
143
|
+
governanceFloor: heads('governanceFloor'),
|
|
144
|
+
nonce: u64('nonce'),
|
|
145
|
+
notAfter: u64('notAfter'),
|
|
146
|
+
signature: hex(take(64, 'signature')),
|
|
147
|
+
};
|
|
148
|
+
if (o !== bytes.length) {
|
|
149
|
+
throw new Error(`governance warrant is ${bytes.length} bytes but its fields account for ${o}`);
|
|
150
|
+
}
|
|
151
|
+
return fields;
|
|
152
|
+
}
|
|
153
|
+
function kindByte(kind) {
|
|
154
|
+
const byte = KIND_BYTE[kind];
|
|
155
|
+
if (byte === undefined) {
|
|
156
|
+
throw new Error(`op kind must be 'group' or 'root', got ${String(kind)}`);
|
|
157
|
+
}
|
|
158
|
+
return byte;
|
|
159
|
+
}
|
package/dist/warrant/index.d.ts
CHANGED
|
@@ -2,3 +2,7 @@ export { signWarrant, intentHash } from './warrant.js';
|
|
|
2
2
|
export type { WarrantInput } from './warrant.js';
|
|
3
3
|
export { signCreationWarrant, parseCreationWarrant, creationInitHash, } from './creation-warrant.js';
|
|
4
4
|
export type { CreationWarrantInput, CreationWarrantFields, SignedCreationWarrant, } from './creation-warrant.js';
|
|
5
|
+
export { signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, } from './governance-warrant.js';
|
|
6
|
+
export type { GovernanceWarrantInput, GovernanceWarrantFields, } from './governance-warrant.js';
|
|
7
|
+
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, } from './governance-op.js';
|
|
8
|
+
export type { GovernanceOp, GovernanceOpKind, GovernanceMemberRole, GroupCreatedInput, } from './governance-op.js';
|
package/dist/warrant/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
// Warrant signing — the author's half of delegated authorship
|
|
2
2
|
export { signWarrant, intentHash } from './warrant.js';
|
|
3
3
|
export { signCreationWarrant, parseCreationWarrant, creationInitHash, } from './creation-warrant.js';
|
|
4
|
+
export { signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, } from './governance-warrant.js';
|
|
5
|
+
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, } from './governance-op.js';
|