@calimero-network/mero-js 22.5.1 → 22.7.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/capabilities.d.ts +42 -0
- package/dist/capabilities.js +64 -0
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +403 -116
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -1
- package/dist/index.mjs +403 -116
- package/dist/index.mjs.map +4 -4
- package/dist/relay/index.d.ts +1 -1
- package/dist/relay/relay-client.d.ts +107 -2
- package/dist/relay/relay-client.js +121 -6
- package/dist/signer/index.d.ts +2 -0
- package/dist/signer/index.js +3 -0
- package/dist/signer/signer.d.ts +92 -0
- package/dist/signer/signer.js +147 -0
- package/dist/warrant/creation-warrant.d.ts +17 -3
- package/dist/warrant/creation-warrant.js +6 -5
- package/dist/warrant/governance-op.d.ts +47 -1
- package/dist/warrant/governance-op.js +62 -2
- package/dist/warrant/governance-warrant.d.ts +17 -3
- package/dist/warrant/governance-warrant.js +6 -5
- package/dist/warrant/index.d.ts +2 -2
- package/dist/warrant/index.js +1 -1
- package/dist/warrant/warrant.d.ts +20 -1
- package/dist/warrant/warrant.js +19 -4
- package/package.json +1 -1
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Sources in core, for the next person to check a discriminant:
|
|
17
17
|
* `crates/governance-types/src/lib.rs` (`GroupOp`, `RootOp`),
|
|
18
|
-
* `crates/primitives/src/context.rs` (`GroupMemberRole`),
|
|
18
|
+
* `crates/primitives/src/context.rs` (`GroupMemberRole`),
|
|
19
|
+
* `crates/account/src/namespace_id.rs` (`founded_namespace_id`), and the vectors in
|
|
19
20
|
* `crates/governance-types/src/tests.rs` (`delegated_governance_op_vectors_are_stable`).
|
|
20
21
|
*/
|
|
21
22
|
/**
|
|
@@ -51,6 +52,15 @@ export declare function memberRemovedOp(member: string): GovernanceOp;
|
|
|
51
52
|
export declare function memberLeftOp(member: string): GovernanceOp;
|
|
52
53
|
/** Set `member`'s role (an account, hex). */
|
|
53
54
|
export declare function memberRoleSetOp(member: string, memberRole: GovernanceMemberRole): GovernanceOp;
|
|
55
|
+
/**
|
|
56
|
+
* Set the group's default capability mask (`MemberCapabilities` bits, a `u32`),
|
|
57
|
+
* the capabilities every member holds without a per-member grant. Needs admin.
|
|
58
|
+
*
|
|
59
|
+
* The relay refuses any change to `CAN_AUTHOR_ON_BEHALF` (bit 9) through this
|
|
60
|
+
* path, and no namespace starts with it in its default mask, so a mask that
|
|
61
|
+
* sets it is refused here rather than burning a nonce on a certain 403.
|
|
62
|
+
*/
|
|
63
|
+
export declare function defaultCapabilitiesSetOp(capabilities: number): GovernanceOp;
|
|
54
64
|
/** What a subgroup is created with. */
|
|
55
65
|
export interface GroupCreatedInput {
|
|
56
66
|
/** The new subgroup's id, hex (32 bytes). Choose it fresh; a taken id is refused. */
|
|
@@ -79,3 +89,39 @@ export declare function groupReparentedOp(childGroupId: string, newParentId: str
|
|
|
79
89
|
* empty, for the relay to enumerate.
|
|
80
90
|
*/
|
|
81
91
|
export declare function groupDeletedOp(rootGroupId: string): GovernanceOp;
|
|
92
|
+
/**
|
|
93
|
+
* The id of the namespace `founder` founds with `salt`: core's
|
|
94
|
+
* `founded_namespace_id`, `domain_hash("calimero.namespace.id.v1", [founder, salt])`.
|
|
95
|
+
*
|
|
96
|
+
* Anyone holding the pair can recompute it, and only the founder's account
|
|
97
|
+
* hashes to it, which is why a relay founding a namespace for a member cannot
|
|
98
|
+
* choose the id: the member computes it and signs it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function foundedNamespaceId(founder: string, salt: string): Promise<string>;
|
|
101
|
+
/** What a namespace is founded with. */
|
|
102
|
+
export interface NamespaceCreatedInput {
|
|
103
|
+
/** The founding account, hex: the **author's**. It becomes founder, owner and admin. */
|
|
104
|
+
founder: string;
|
|
105
|
+
/**
|
|
106
|
+
* The founder's `AccountProof<DeviceCert>`, hex: the credential the author
|
|
107
|
+
* already presents as `authorProof`. Its account must be `founder` and its
|
|
108
|
+
* device key the one that signs the warrant; the genesis apply checks both
|
|
109
|
+
* and binds the founder's device from it.
|
|
110
|
+
*/
|
|
111
|
+
credential: string;
|
|
112
|
+
/**
|
|
113
|
+
* The salt the namespace id is derived with, hex (32 bytes). Choose it at
|
|
114
|
+
* random; {@link foundedNamespaceId} gives the id it yields.
|
|
115
|
+
*/
|
|
116
|
+
salt: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Found a namespace: core's `RootOp::NamespaceCreatedV2` genesis, a root op
|
|
120
|
+
* published on the new namespace's own log.
|
|
121
|
+
*
|
|
122
|
+
* Its delegable form is the op itself, nothing cleared, so the author signs the
|
|
123
|
+
* exact genesis. Borsh is `[9] || founder || credential || salt`: the credential
|
|
124
|
+
* is a nested struct, not a length-prefixed byte string, so it is written
|
|
125
|
+
* inline. The warrant's scope must be {@link foundedNamespaceId}`(founder, salt)`.
|
|
126
|
+
*/
|
|
127
|
+
export declare function namespaceCreatedOp(input: NamespaceCreatedInput): GovernanceOp;
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Sources in core, for the next person to check a discriminant:
|
|
17
17
|
* `crates/governance-types/src/lib.rs` (`GroupOp`, `RootOp`),
|
|
18
|
-
* `crates/primitives/src/context.rs` (`GroupMemberRole`),
|
|
18
|
+
* `crates/primitives/src/context.rs` (`GroupMemberRole`),
|
|
19
|
+
* `crates/account/src/namespace_id.rs` (`founded_namespace_id`), and the vectors in
|
|
19
20
|
* `crates/governance-types/src/tests.rs` (`delegated_governance_op_vectors_are_stable`).
|
|
20
21
|
*/
|
|
21
|
-
import {
|
|
22
|
+
import { CAPABILITIES, hasCap } from '../capabilities.js';
|
|
23
|
+
import { concat, domainHash, fromHex, fromHexUnsized, hex, u32le } from '../crypto/internal.js';
|
|
22
24
|
/**
|
|
23
25
|
* `GroupMemberRole` discriminants. The two TEE roles (3 and 4) are minted only
|
|
24
26
|
* by attestation admission and never by a member's op, so they are not offered.
|
|
@@ -34,13 +36,17 @@ const GROUP_OP = {
|
|
|
34
36
|
MemberRemoved: 2,
|
|
35
37
|
MemberLeft: 3,
|
|
36
38
|
MemberRoleSet: 4,
|
|
39
|
+
DefaultCapabilitiesSet: 6,
|
|
37
40
|
};
|
|
38
41
|
/** `RootOp` discriminants, by position in core's enum. */
|
|
39
42
|
const ROOT_OP = {
|
|
40
43
|
GroupCreated: 0,
|
|
41
44
|
GroupReparented: 1,
|
|
42
45
|
GroupDeleted: 2,
|
|
46
|
+
NamespaceCreatedV2: 9,
|
|
43
47
|
};
|
|
48
|
+
/** core's `NAMESPACE_ID_DOMAIN`, which a founded namespace id is hashed under. */
|
|
49
|
+
const NAMESPACE_ID_DOMAIN = new TextEncoder().encode('calimero.namespace.id.v1');
|
|
44
50
|
/** A post-state hash left for the relay to compute: 32 zero bytes. */
|
|
45
51
|
const CLEARED_HASH = new Uint8Array(32);
|
|
46
52
|
/** An empty borsh `Vec`: a zero `u32` count. */
|
|
@@ -94,6 +100,27 @@ export function memberRoleSetOp(member, memberRole) {
|
|
|
94
100
|
bytes: concat(tag(GROUP_OP.MemberRoleSet), fromHex(member, 'member', 32), role(memberRole)),
|
|
95
101
|
};
|
|
96
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Set the group's default capability mask (`MemberCapabilities` bits, a `u32`),
|
|
105
|
+
* the capabilities every member holds without a per-member grant. Needs admin.
|
|
106
|
+
*
|
|
107
|
+
* The relay refuses any change to `CAN_AUTHOR_ON_BEHALF` (bit 9) through this
|
|
108
|
+
* path, and no namespace starts with it in its default mask, so a mask that
|
|
109
|
+
* sets it is refused here rather than burning a nonce on a certain 403.
|
|
110
|
+
*/
|
|
111
|
+
export function defaultCapabilitiesSetOp(capabilities) {
|
|
112
|
+
if (!Number.isInteger(capabilities) || capabilities < 0 || capabilities > 4294967295) {
|
|
113
|
+
throw new Error(`capabilities must be a u32 bit mask, got ${String(capabilities)}`);
|
|
114
|
+
}
|
|
115
|
+
// Never changed through a relay.
|
|
116
|
+
if (hasCap(capabilities, CAPABILITIES.CAN_AUTHOR_ON_BEHALF)) {
|
|
117
|
+
throw new Error('capabilities may not include CAN_AUTHOR_ON_BEHALF (512): a relay never carries a change to it');
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
kind: 'group',
|
|
121
|
+
bytes: concat(tag(GROUP_OP.DefaultCapabilitiesSet), u32le(capabilities)),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
97
124
|
/**
|
|
98
125
|
* Create a subgroup under `parentId`, a root op published on the namespace.
|
|
99
126
|
*
|
|
@@ -123,3 +150,36 @@ export function groupDeletedOp(rootGroupId) {
|
|
|
123
150
|
bytes: concat(tag(ROOT_OP.GroupDeleted), fromHex(rootGroupId, 'rootGroupId', 32), EMPTY_VEC, EMPTY_VEC),
|
|
124
151
|
};
|
|
125
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* The id of the namespace `founder` founds with `salt`: core's
|
|
155
|
+
* `founded_namespace_id`, `domain_hash("calimero.namespace.id.v1", [founder, salt])`.
|
|
156
|
+
*
|
|
157
|
+
* Anyone holding the pair can recompute it, and only the founder's account
|
|
158
|
+
* hashes to it, which is why a relay founding a namespace for a member cannot
|
|
159
|
+
* choose the id: the member computes it and signs it.
|
|
160
|
+
*/
|
|
161
|
+
export async function foundedNamespaceId(founder, salt) {
|
|
162
|
+
return hex(await domainHash(NAMESPACE_ID_DOMAIN, [
|
|
163
|
+
fromHex(founder, 'founder', 32),
|
|
164
|
+
fromHex(salt, 'salt', 32),
|
|
165
|
+
]));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Found a namespace: core's `RootOp::NamespaceCreatedV2` genesis, a root op
|
|
169
|
+
* published on the new namespace's own log.
|
|
170
|
+
*
|
|
171
|
+
* Its delegable form is the op itself, nothing cleared, so the author signs the
|
|
172
|
+
* exact genesis. Borsh is `[9] || founder || credential || salt`: the credential
|
|
173
|
+
* is a nested struct, not a length-prefixed byte string, so it is written
|
|
174
|
+
* inline. The warrant's scope must be {@link foundedNamespaceId}`(founder, salt)`.
|
|
175
|
+
*/
|
|
176
|
+
export function namespaceCreatedOp(input) {
|
|
177
|
+
const credential = fromHexUnsized(input.credential, 'credential');
|
|
178
|
+
if (credential.length === 0) {
|
|
179
|
+
throw new Error('credential must be the founder\'s AccountProof<DeviceCert>, got nothing');
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
kind: 'root',
|
|
183
|
+
bytes: concat(tag(ROOT_OP.NamespaceCreatedV2), fromHex(input.founder, 'founder', 32), credential, fromHex(input.salt, 'salt', 32)),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* `crates/account/src/tests/governance_wire_fixture.rs`, and this module's test
|
|
27
27
|
* asserts the same vectors. A drift would surface at a relay as a 400.
|
|
28
28
|
*/
|
|
29
|
+
import { type Signer } from '../signer/signer.js';
|
|
29
30
|
import type { GovernanceOp, GovernanceOpKind } from './governance-op.js';
|
|
30
31
|
/** What a governance warrant authorises. */
|
|
31
32
|
export interface GovernanceWarrantInput {
|
|
@@ -51,15 +52,28 @@ export interface GovernanceWarrantInput {
|
|
|
51
52
|
accountHeads?: string[];
|
|
52
53
|
/** Governance heads the author's view descended from, each hex (32 bytes). At most 64. */
|
|
53
54
|
governanceFloor?: string[];
|
|
54
|
-
/**
|
|
55
|
-
|
|
55
|
+
/**
|
|
56
|
+
* The author device's ed25519 signing secret, hex (32 bytes). Never sent.
|
|
57
|
+
*
|
|
58
|
+
* Mutually exclusive with {@link GovernanceWarrantInput.signer}: exactly one is required.
|
|
59
|
+
* Prefer `signer` in a browser, since a secret held as a string is readable
|
|
60
|
+
* by anything on the origin.
|
|
61
|
+
*/
|
|
62
|
+
deviceSecret?: string;
|
|
63
|
+
/**
|
|
64
|
+
* The author device's signer, in place of {@link GovernanceWarrantInput.deviceSecret}: for a
|
|
65
|
+
* key that cannot be exported to hex (a non-extractable WebCrypto key, a
|
|
66
|
+
* passkey, a hardware or remote signer). Its `publicKey` becomes the
|
|
67
|
+
* warrant's `author_device_key`, and it signs the 32-byte preimage as is.
|
|
68
|
+
*/
|
|
69
|
+
signer?: Signer;
|
|
56
70
|
}
|
|
57
71
|
/** A governance warrant's fields, decoded from the wire encoding. */
|
|
58
72
|
export interface GovernanceWarrantFields {
|
|
59
73
|
scope: string;
|
|
60
74
|
kind: GovernanceOpKind;
|
|
61
75
|
authorAccount: string;
|
|
62
|
-
/** The author device key
|
|
76
|
+
/** The author device key: the signer's public key. */
|
|
63
77
|
deviceKey: string;
|
|
64
78
|
executor: string;
|
|
65
79
|
opHash: string;
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
* `crates/account/src/tests/governance_wire_fixture.rs`, and this module's test
|
|
27
27
|
* asserts the same vectors. A drift would surface at a relay as a 400.
|
|
28
28
|
*/
|
|
29
|
-
import { concat,
|
|
30
|
-
import {
|
|
29
|
+
import { concat, domainHash, fromHex, hex, u32le, u64le } from '../crypto/internal.js';
|
|
30
|
+
import { resolveSigner } from '../signer/signer.js';
|
|
31
|
+
import { checkedSignature, citedHeads } from './warrant.js';
|
|
31
32
|
const SIGN_DOMAIN = new TextEncoder().encode('calimero.governance-warrant.v1');
|
|
32
33
|
const OP_DOMAIN = new TextEncoder().encode('calimero.governance-warrant.op.v1');
|
|
33
34
|
/** core's `GovernanceOpKind` discriminants. */
|
|
@@ -72,8 +73,8 @@ export async function signGovernanceWarrant(input) {
|
|
|
72
73
|
const accountHeads = citedHeads(input.accountHeads, 'accountHeads');
|
|
73
74
|
const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');
|
|
74
75
|
const kind = kindByte(input.op.kind);
|
|
75
|
-
const
|
|
76
|
-
const deviceKey =
|
|
76
|
+
const signer = await resolveSigner(input.deviceSecret, input.signer, 'deviceSecret');
|
|
77
|
+
const deviceKey = fromHex(signer.publicKey, 'signer.publicKey', 32);
|
|
77
78
|
const opHash = await governanceOpHash(input.op);
|
|
78
79
|
const preimage = await governanceWarrantPreimage({
|
|
79
80
|
scope,
|
|
@@ -87,7 +88,7 @@ export async function signGovernanceWarrant(input) {
|
|
|
87
88
|
nonce: input.nonce,
|
|
88
89
|
notAfter: input.notAfter,
|
|
89
90
|
});
|
|
90
|
-
const signature =
|
|
91
|
+
const signature = checkedSignature(await signer.sign(preimage));
|
|
91
92
|
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
|
}
|
|
93
94
|
/**
|
package/dist/warrant/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { signCreationWarrant, parseCreationWarrant, creationInitHash, } from './
|
|
|
4
4
|
export type { CreationWarrantInput, CreationWarrantFields, SignedCreationWarrant, } from './creation-warrant.js';
|
|
5
5
|
export { signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, } from './governance-warrant.js';
|
|
6
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';
|
|
7
|
+
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, namespaceCreatedOp, defaultCapabilitiesSetOp, foundedNamespaceId, } from './governance-op.js';
|
|
8
|
+
export type { GovernanceOp, GovernanceOpKind, GovernanceMemberRole, GroupCreatedInput, NamespaceCreatedInput, } from './governance-op.js';
|
package/dist/warrant/index.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
export { signWarrant, intentHash } from './warrant.js';
|
|
3
3
|
export { signCreationWarrant, parseCreationWarrant, creationInitHash, } from './creation-warrant.js';
|
|
4
4
|
export { signGovernanceWarrant, parseGovernanceWarrant, governanceOpHash, } from './governance-warrant.js';
|
|
5
|
-
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, } from './governance-op.js';
|
|
5
|
+
export { memberAddedOp, memberRemovedOp, memberLeftOp, memberRoleSetOp, groupCreatedOp, groupReparentedOp, groupDeletedOp, namespaceCreatedOp, defaultCapabilitiesSetOp, foundedNamespaceId, } from './governance-op.js';
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* this producing well-formed warrants whose signatures verify nowhere, surfacing
|
|
28
28
|
* at a relay as a 403: an authorization refusal, nowhere near the cause.
|
|
29
29
|
*/
|
|
30
|
+
import { type Signer } from '../signer/signer.js';
|
|
30
31
|
/** Ed25519 PKCS#8 prefix, so a raw 32-byte seed can be imported by WebCrypto. */
|
|
31
32
|
/** What a warrant authorises. */
|
|
32
33
|
export interface WarrantInput {
|
|
@@ -103,8 +104,18 @@ export interface WarrantInput {
|
|
|
103
104
|
*
|
|
104
105
|
* Never sent anywhere. It signs locally and only the signature travels, which
|
|
105
106
|
* is the whole reason a warrant can be minted by something holding no node.
|
|
107
|
+
*
|
|
108
|
+
* Mutually exclusive with {@link WarrantInput.signer}: exactly one is
|
|
109
|
+
* required. Prefer `signer` in a browser, since a secret held as a string is
|
|
110
|
+
* readable by anything on the origin.
|
|
111
|
+
*/
|
|
112
|
+
deviceSecret?: string;
|
|
113
|
+
/**
|
|
114
|
+
* The author device's signer, in place of {@link WarrantInput.deviceSecret}:
|
|
115
|
+
* for a key that cannot be exported to hex (a non-extractable WebCrypto key,
|
|
116
|
+
* a passkey, a hardware or remote signer).
|
|
106
117
|
*/
|
|
107
|
-
|
|
118
|
+
signer?: Signer;
|
|
108
119
|
}
|
|
109
120
|
/**
|
|
110
121
|
* The commitment a warrant carries in place of the intent itself.
|
|
@@ -125,6 +136,14 @@ export declare function signWarrant(input: WarrantInput): Promise<string>;
|
|
|
125
136
|
* Decode a cited-head list, refusing one longer than a node will accept.
|
|
126
137
|
* Shared with `creation-warrant.ts`; internal, not exported from the package root.
|
|
127
138
|
*/
|
|
139
|
+
/**
|
|
140
|
+
* A signer's output, refused unless it is a 64-byte Ed25519 signature.
|
|
141
|
+
*
|
|
142
|
+
* The signature is spliced into the encoding at a fixed width, so a signer that
|
|
143
|
+
* returned anything else would yield a warrant that parses as garbage at a
|
|
144
|
+
* relay, far from the signer at fault. Internal.
|
|
145
|
+
*/
|
|
146
|
+
export declare function checkedSignature(signature: Uint8Array): Uint8Array;
|
|
128
147
|
export declare function citedHeads(heads: string[] | undefined, label: string): Uint8Array[];
|
|
129
148
|
/** A warrant's fields, as hex, decoded from the wire encoding. */
|
|
130
149
|
export interface WarrantFields {
|
package/dist/warrant/warrant.js
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
* this producing well-formed warrants whose signatures verify nowhere, surfacing
|
|
28
28
|
* at a relay as a 403: an authorization refusal, nowhere near the cause.
|
|
29
29
|
*/
|
|
30
|
-
import { concat,
|
|
30
|
+
import { concat, domainHash, fromHex, hex, u32le, u64le } from '../crypto/internal.js';
|
|
31
|
+
import { resolveSigner } from '../signer/signer.js';
|
|
31
32
|
const SIGN_DOMAIN = new TextEncoder().encode('calimero.warrant.v2');
|
|
32
33
|
const INTENT_DOMAIN = new TextEncoder().encode('calimero.warrant.intent.v1');
|
|
33
34
|
/**
|
|
@@ -61,8 +62,8 @@ export async function signWarrant(input) {
|
|
|
61
62
|
const appVersion = fromHex(input.appVersion ?? UNPINNED_APP, 'appVersion', 32);
|
|
62
63
|
const accountHeads = citedHeads(input.accountHeads, 'accountHeads');
|
|
63
64
|
const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');
|
|
64
|
-
const
|
|
65
|
-
const publicKey =
|
|
65
|
+
const signer = await resolveSigner(input.deviceSecret, input.signer, 'deviceSecret');
|
|
66
|
+
const publicKey = fromHex(signer.publicKey, 'signer.publicKey', 32);
|
|
66
67
|
const method = new TextEncoder().encode(input.method);
|
|
67
68
|
const commitment = await intentHash(input.method, input.argsJson);
|
|
68
69
|
const nonce = u64le(input.nonce);
|
|
@@ -87,7 +88,7 @@ export async function signWarrant(input) {
|
|
|
87
88
|
nonce,
|
|
88
89
|
notAfter,
|
|
89
90
|
]);
|
|
90
|
-
const signature =
|
|
91
|
+
const signature = checkedSignature(await signer.sign(preimage));
|
|
91
92
|
// Borsh: a `String` is a u32 length then its UTF-8 bytes; a `Vec<[u8; 32]>` is
|
|
92
93
|
// a u32 count then the elements. u32 HERE; u64 in the preimage above.
|
|
93
94
|
return hex(concat(context, authorAccount, publicKey, executor, appVersion, u32le(method.length), method, commitment, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, nonce, notAfter, signature));
|
|
@@ -96,6 +97,20 @@ export async function signWarrant(input) {
|
|
|
96
97
|
* Decode a cited-head list, refusing one longer than a node will accept.
|
|
97
98
|
* Shared with `creation-warrant.ts`; internal, not exported from the package root.
|
|
98
99
|
*/
|
|
100
|
+
/**
|
|
101
|
+
* A signer's output, refused unless it is a 64-byte Ed25519 signature.
|
|
102
|
+
*
|
|
103
|
+
* The signature is spliced into the encoding at a fixed width, so a signer that
|
|
104
|
+
* returned anything else would yield a warrant that parses as garbage at a
|
|
105
|
+
* relay, far from the signer at fault. Internal.
|
|
106
|
+
*/
|
|
107
|
+
export function checkedSignature(signature) {
|
|
108
|
+
if (!(signature instanceof Uint8Array) || signature.length !== 64) {
|
|
109
|
+
const got = signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature;
|
|
110
|
+
throw new Error(`signer returned ${got}, expected a 64-byte Ed25519 signature`);
|
|
111
|
+
}
|
|
112
|
+
return signature;
|
|
113
|
+
}
|
|
99
114
|
export function citedHeads(heads, label) {
|
|
100
115
|
const list = heads ?? [];
|
|
101
116
|
if (list.length > MAX_CITED_HEADS) {
|