@calimero-network/mero-js 26.10.0 → 27.0.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 +73 -288
- package/dist/cloud-v2/attestation.d.ts +91 -0
- package/dist/cloud-v2/attestation.js +135 -0
- package/dist/cloud-v2/capabilities.d.ts +30 -0
- package/dist/cloud-v2/capabilities.js +31 -0
- package/dist/cloud-v2/client.d.ts +426 -0
- package/dist/cloud-v2/client.js +646 -0
- package/dist/cloud-v2/errors.d.ts +98 -0
- package/dist/cloud-v2/errors.js +165 -0
- package/dist/cloud-v2/index.d.ts +10 -0
- package/dist/cloud-v2/index.js +5 -0
- package/dist/cloud-v2/passkey.d.ts +27 -0
- package/dist/cloud-v2/passkey.js +73 -0
- package/dist/crypto/internal.d.ts +3 -37
- package/dist/crypto/internal.js +3 -72
- package/dist/index.browser.mjs +4 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +1750 -12767
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -56
- package/dist/index.js +14 -75
- package/dist/index.mjs +1750 -12767
- package/dist/index.mjs.map +4 -4
- package/dist/sealed/index.d.ts +4 -4
- package/dist/sealed/index.js +2 -2
- package/dist/sealed/release.d.ts +21 -76
- package/dist/sealed/release.js +24 -108
- package/dist/sealed/sealed.d.ts +8 -56
- package/dist/sealed/sealed.js +11 -89
- package/dist/sealed/verify.d.ts +7 -13
- package/dist/sealed/verify.js +3 -7
- package/package.json +8 -13
- package/dist/account/account-admin.d.ts +0 -80
- package/dist/account/account-admin.js +0 -346
- package/dist/account/account.d.ts +0 -196
- package/dist/account/account.js +0 -269
- package/dist/account/application-id.d.ts +0 -50
- package/dist/account/application-id.js +0 -83
- package/dist/account/bootstrap-from-invitation.d.ts +0 -110
- package/dist/account/bootstrap-from-invitation.js +0 -80
- package/dist/account/create-context.d.ts +0 -103
- package/dist/account/create-context.js +0 -267
- package/dist/account/govern.d.ts +0 -24
- package/dist/account/govern.js +0 -50
- package/dist/account/index.d.ts +0 -26
- package/dist/account/index.js +0 -15
- package/dist/account/join-as-account.d.ts +0 -23
- package/dist/account/join-as-account.js +0 -34
- package/dist/account/join-with-node.d.ts +0 -115
- package/dist/account/join-with-node.js +0 -161
- package/dist/account/node-admin.d.ts +0 -20
- package/dist/account/node-admin.js +0 -33
- package/dist/account/relay-from-invitation.d.ts +0 -137
- package/dist/account/relay-from-invitation.js +0 -180
- package/dist/account/relay-login-error.d.ts +0 -32
- package/dist/account/relay-login-error.js +0 -34
- package/dist/account/session.d.ts +0 -266
- package/dist/account/session.js +0 -915
- package/dist/account/wordlist.d.ts +0 -16
- package/dist/account/wordlist.js +0 -273
- package/dist/admin-api/admin-client.d.ts +0 -686
- package/dist/admin-api/admin-client.js +0 -1225
- package/dist/admin-api/admin-factory.d.ts +0 -14
- package/dist/admin-api/admin-factory.js +0 -22
- package/dist/admin-api/admin-types.d.ts +0 -2041
- package/dist/admin-api/admin-types.js +0 -12
- package/dist/admin-api/index.d.ts +0 -4
- package/dist/admin-api/index.js +0 -5
- package/dist/admin-api/redeem-invitation.d.ts +0 -101
- package/dist/admin-api/redeem-invitation.js +0 -200
- package/dist/auth/index.d.ts +0 -34
- package/dist/auth/index.js +0 -67
- package/dist/auth-api/auth-client.d.ts +0 -31
- package/dist/auth-api/auth-client.js +0 -117
- package/dist/auth-api/auth-factory.d.ts +0 -7
- package/dist/auth-api/auth-factory.js +0 -41
- package/dist/auth-api/auth-types.d.ts +0 -130
- package/dist/auth-api/auth-types.js +0 -2
- package/dist/auth-api/index.d.ts +0 -3
- package/dist/auth-api/index.js +0 -4
- package/dist/capabilities.d.ts +0 -84
- package/dist/capabilities.js +0 -117
- package/dist/cloud/account-ownership.d.ts +0 -63
- package/dist/cloud/account-ownership.js +0 -87
- package/dist/cloud/cloud-client.d.ts +0 -949
- package/dist/cloud/cloud-client.js +0 -1068
- package/dist/cloud/connect.d.ts +0 -213
- package/dist/cloud/connect.js +0 -260
- package/dist/cloud/enrol-redirect.d.ts +0 -181
- package/dist/cloud/enrol-redirect.js +0 -244
- package/dist/cloud/index.d.ts +0 -12
- package/dist/cloud/index.js +0 -10
- package/dist/cloud/link-redirect.d.ts +0 -95
- package/dist/cloud/link-redirect.js +0 -121
- package/dist/cloud/link-state.d.ts +0 -36
- package/dist/cloud/link-state.js +0 -104
- package/dist/cloud/routing-proof.d.ts +0 -113
- package/dist/cloud/routing-proof.js +0 -96
- package/dist/device-cert/device-cert.d.ts +0 -189
- package/dist/device-cert/device-cert.js +0 -239
- package/dist/device-cert/device-scope.d.ts +0 -58
- package/dist/device-cert/device-scope.js +0 -76
- package/dist/device-cert/index.d.ts +0 -4
- package/dist/device-cert/index.js +0 -3
- package/dist/ephemeral/codec.d.ts +0 -4
- package/dist/ephemeral/codec.js +0 -12
- package/dist/ephemeral/index.d.ts +0 -57
- package/dist/ephemeral/index.js +0 -101
- package/dist/ephemeral/types.d.ts +0 -27
- package/dist/ephemeral/types.js +0 -1
- package/dist/errors/classify.d.ts +0 -65
- package/dist/errors/classify.js +0 -117
- package/dist/errors/index.d.ts +0 -2
- package/dist/errors/index.js +0 -1
- package/dist/events/group.d.ts +0 -95
- package/dist/events/group.js +0 -10
- package/dist/events/index.d.ts +0 -5
- package/dist/events/index.js +0 -2
- package/dist/events/sse.d.ts +0 -127
- package/dist/events/sse.js +0 -469
- package/dist/events/ws.d.ts +0 -57
- package/dist/events/ws.js +0 -212
- package/dist/http-client/api-response.d.ts +0 -15
- package/dist/http-client/api-response.js +0 -1
- package/dist/http-client/http-factory.d.ts +0 -71
- package/dist/http-client/http-factory.js +0 -68
- package/dist/http-client/http-types.d.ts +0 -87
- package/dist/http-client/http-types.js +0 -1
- package/dist/http-client/index.d.ts +0 -7
- package/dist/http-client/index.js +0 -11
- package/dist/http-client/path-segment.d.ts +0 -1
- package/dist/http-client/path-segment.js +0 -8
- package/dist/http-client/retry.d.ts +0 -5
- package/dist/http-client/retry.js +0 -82
- package/dist/http-client/signal-utils.d.ts +0 -2
- package/dist/http-client/signal-utils.js +0 -41
- package/dist/http-client/web-client.d.ts +0 -80
- package/dist/http-client/web-client.js +0 -557
- package/dist/invitation/index.d.ts +0 -2
- package/dist/invitation/index.js +0 -2
- package/dist/invitation/invitation.d.ts +0 -106
- package/dist/invitation/invitation.js +0 -104
- package/dist/login/index.d.ts +0 -4
- package/dist/login/index.js +0 -3
- package/dist/login/login.d.ts +0 -111
- package/dist/login/login.js +0 -110
- package/dist/login/session.d.ts +0 -124
- package/dist/login/session.js +0 -141
- package/dist/mero-js.d.ts +0 -231
- package/dist/mero-js.js +0 -469
- package/dist/namespace-op/index.d.ts +0 -2
- package/dist/namespace-op/index.js +0 -3
- package/dist/namespace-op/namespace-op.d.ts +0 -150
- package/dist/namespace-op/namespace-op.js +0 -191
- package/dist/nodeDiscovery.d.ts +0 -47
- package/dist/nodeDiscovery.js +0 -101
- package/dist/owner-op/index.d.ts +0 -2
- package/dist/owner-op/index.js +0 -3
- package/dist/owner-op/owner-op.d.ts +0 -119
- package/dist/owner-op/owner-op.js +0 -214
- package/dist/presence/relay-presence.d.ts +0 -56
- package/dist/presence/relay-presence.js +0 -107
- package/dist/presence/statement.d.ts +0 -12
- package/dist/presence/statement.js +0 -17
- package/dist/relay/index.d.ts +0 -4
- package/dist/relay/index.js +0 -2
- package/dist/relay/nonce-source.d.ts +0 -72
- package/dist/relay/nonce-source.js +0 -111
- package/dist/relay/relay-client.d.ts +0 -472
- package/dist/relay/relay-client.js +0 -518
- package/dist/relay-attestation/attest-node-key.d.ts +0 -66
- package/dist/relay-attestation/attest-node-key.js +0 -110
- package/dist/relay-attestation/index.d.ts +0 -1
- package/dist/relay-attestation/index.js +0 -1
- package/dist/request/caller-proof.d.ts +0 -43
- package/dist/request/caller-proof.js +0 -36
- package/dist/request/index.d.ts +0 -3
- package/dist/request/index.js +0 -3
- package/dist/request/proof-signer.d.ts +0 -49
- package/dist/request/proof-signer.js +0 -59
- package/dist/request/request-sig.d.ts +0 -99
- package/dist/request/request-sig.js +0 -95
- package/dist/request-proof/authorizer.d.ts +0 -41
- package/dist/request-proof/authorizer.js +0 -60
- package/dist/request-proof/index.d.ts +0 -4
- package/dist/request-proof/index.js +0 -2
- package/dist/request-proof/request-proof.d.ts +0 -53
- package/dist/request-proof/request-proof.js +0 -95
- package/dist/rpc/index.d.ts +0 -64
- package/dist/rpc/index.js +0 -78
- package/dist/signer/index.d.ts +0 -2
- package/dist/signer/index.js +0 -3
- package/dist/signer/signer.d.ts +0 -92
- package/dist/signer/signer.js +0 -147
- package/dist/token-store/index.d.ts +0 -27
- package/dist/token-store/index.js +0 -62
- package/dist/transport/index.d.ts +0 -6
- package/dist/transport/index.js +0 -3
- package/dist/transport/mero-client.d.ts +0 -294
- package/dist/transport/mero-client.js +0 -386
- package/dist/transport/relay-observer.d.ts +0 -152
- package/dist/transport/relay-observer.js +0 -173
- package/dist/transport/relay-transport.d.ts +0 -73
- package/dist/transport/relay-transport.js +0 -94
- package/dist/transport/types.d.ts +0 -100
- package/dist/transport/types.js +0 -32
- package/dist/types/api-response.d.ts +0 -15
- package/dist/types/api-response.js +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/warrant/creation-warrant.d.ts +0 -163
- package/dist/warrant/creation-warrant.js +0 -194
- package/dist/warrant/governance-op.d.ts +0 -202
- package/dist/warrant/governance-op.js +0 -355
- package/dist/warrant/governance-warrant.d.ts +0 -124
- package/dist/warrant/governance-warrant.js +0 -164
- package/dist/warrant/index.d.ts +0 -8
- package/dist/warrant/index.js +0 -5
- package/dist/warrant/warrant.d.ts +0 -188
- package/dist/warrant/warrant.js +0 -202
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mint a warrant: the author's consent for one relay to run one intent, once.
|
|
3
|
-
*
|
|
4
|
-
* This exists because the case delegated authorship is *for* is an account that
|
|
5
|
-
* holds no node — a browser tab, a Node service, a bot. Every one of those is a
|
|
6
|
-
* JS runtime, and until now the only thing that could mint a warrant was `merod`,
|
|
7
|
-
* so "a member with no node" in practice meant "a member with a node it is not
|
|
8
|
-
* allowed to use for anything else".
|
|
9
|
-
*
|
|
10
|
-
* **No dependencies, and that is not incidental.** The primitives are all built
|
|
11
|
-
* in: `crypto.subtle.digest('SHA-256')` for the hash, `crypto.subtle` Ed25519
|
|
12
|
-
* for the signature, and `DataView` for the integers.
|
|
13
|
-
*
|
|
14
|
-
* Warrant v2 (core#3933) gave up the fixed-width encoding that used to make this
|
|
15
|
-
* pure concatenation: `method` travels in the clear as a string and each cited
|
|
16
|
-
* head list is a vector, so three borsh `u32` counts now sit inside the bytes.
|
|
17
|
-
* Two widths to keep straight, and they are not the same: the counts **inside
|
|
18
|
-
* the encoding** are `u32`, while the counts the **signing preimage** hashes are
|
|
19
|
-
* `u64`. Same numbers, different serialization — reusing one for the other
|
|
20
|
-
* produces a warrant that is well-formed and verifies nowhere.
|
|
21
|
-
*
|
|
22
|
-
* **The byte contract is pinned in core**, at
|
|
23
|
-
* `crates/account/src/tests/warrant_wire_fixture.rs`. The domain strings below
|
|
24
|
-
* are `pub(crate)` there, so nothing in that repository forces anyone to notice
|
|
25
|
-
* this file depends on them — which is exactly why the fixture lives on that side
|
|
26
|
-
* and this module's test asserts the same vectors. A drift would otherwise leave
|
|
27
|
-
* this producing well-formed warrants whose signatures verify nowhere, surfacing
|
|
28
|
-
* at a relay as a 403: an authorization refusal, nowhere near the cause.
|
|
29
|
-
*/
|
|
30
|
-
import { type Signer } from '../signer/signer.js';
|
|
31
|
-
/** Ed25519 PKCS#8 prefix, so a raw 32-byte seed can be imported by WebCrypto. */
|
|
32
|
-
/** What a warrant authorises. */
|
|
33
|
-
export interface WarrantInput {
|
|
34
|
-
/**
|
|
35
|
-
* The context, hex — the same string every other method here takes.
|
|
36
|
-
*
|
|
37
|
-
* Hex like everything else on this interface. It was base58 while the node
|
|
38
|
-
* spelled `ContextId` and `PublicKey` that way and `AccountId` and `DeviceId`
|
|
39
|
-
* in hex; that split is gone, so this module no longer needs a rule per field
|
|
40
|
-
* and no longer needs a base58 decoder to enforce one.
|
|
41
|
-
*/
|
|
42
|
-
context: string;
|
|
43
|
-
/** The author's account, hex — whose consent this is. */
|
|
44
|
-
authorAccount: string;
|
|
45
|
-
/** The relay's account, hex: the subject its authorship grant is held by. */
|
|
46
|
-
executor: string;
|
|
47
|
-
/**
|
|
48
|
-
* The one signing key of `executor` that may spend this warrant, hex: the
|
|
49
|
-
* relay's `executorKey` from discovery. Any other key of the account is refused.
|
|
50
|
-
*/
|
|
51
|
-
executorKey: string;
|
|
52
|
-
/**
|
|
53
|
-
* The blob id of the release the context's group names, hex (32 bytes): the
|
|
54
|
-
* relay's `releaseBytecodeId` from discovery.
|
|
55
|
-
*
|
|
56
|
-
* Pins the code, so a relay cannot wait for an upgrade that widens what
|
|
57
|
-
* `method` does and then spend a warrant signed against the narrower one. The
|
|
58
|
-
* relay refuses a warrant pinning any release other than the one it runs.
|
|
59
|
-
*/
|
|
60
|
-
releaseBytecodeId: string;
|
|
61
|
-
/**
|
|
62
|
-
* That release's semver, discovery's `releaseVersion`: signed, never compared,
|
|
63
|
-
* at most 256 bytes of UTF-8. Defaults to empty, as `merod account warrant` does.
|
|
64
|
-
*/
|
|
65
|
-
releaseVersion?: string;
|
|
66
|
-
/**
|
|
67
|
-
* The method the relay may run.
|
|
68
|
-
*
|
|
69
|
-
* Carried in the clear as of v2, so a peer can select a per-method write-set
|
|
70
|
-
* without reversing a hash against the app's ABI. The *arguments* are still
|
|
71
|
-
* committed to rather than carried — see {@link intentHash}.
|
|
72
|
-
*/
|
|
73
|
-
method: string;
|
|
74
|
-
/** Its arguments, as the JSON the guest will receive. */
|
|
75
|
-
argsJson: unknown;
|
|
76
|
-
/**
|
|
77
|
-
* Monotonic per author **device**.
|
|
78
|
-
*
|
|
79
|
-
* Per device rather than per account because two devices of one account are
|
|
80
|
-
* independent replicas: they cannot coordinate on a counter, so an
|
|
81
|
-
* account-scoped sequence would have them refusing each other's warrants.
|
|
82
|
-
*/
|
|
83
|
-
nonce: number | bigint;
|
|
84
|
-
/**
|
|
85
|
-
* Account-log heads this author saw when signing, each hex (32 bytes).
|
|
86
|
-
*
|
|
87
|
-
* Empty by default, and empty is the honest answer for a client that tracks
|
|
88
|
-
* no log — a fabricated view is worse than none. At most
|
|
89
|
-
* {@link MAX_CITED_HEADS}.
|
|
90
|
-
*/
|
|
91
|
-
accountHeads?: string[];
|
|
92
|
-
/**
|
|
93
|
-
* Governance heads this author's view descended from, each hex (32 bytes).
|
|
94
|
-
*
|
|
95
|
-
* **Where these come from decides whether they mean anything.** A floor the
|
|
96
|
-
* relay supplied is a floor the relay chose: a relay that withholds a
|
|
97
|
-
* governance op can hand you a stale view and collect a warrant citing it.
|
|
98
|
-
* Take the value from the account's own devices or a co-signing peer outside
|
|
99
|
-
* the relay's operator. As it stands this detects an honest relay's staleness
|
|
100
|
-
* and does not constrain a dishonest one.
|
|
101
|
-
*/
|
|
102
|
-
governanceFloor?: string[];
|
|
103
|
-
/** Unix seconds after which the relay must refuse it. */
|
|
104
|
-
notAfter: number | bigint;
|
|
105
|
-
/**
|
|
106
|
-
* The author device's ed25519 signing secret, hex (32 bytes).
|
|
107
|
-
*
|
|
108
|
-
* Never sent anywhere. It signs locally and only the signature travels, which
|
|
109
|
-
* is the whole reason a warrant can be minted by something holding no node.
|
|
110
|
-
*
|
|
111
|
-
* Mutually exclusive with {@link WarrantInput.signer}: exactly one is
|
|
112
|
-
* required. Prefer `signer` in a browser, since a secret held as a string is
|
|
113
|
-
* readable by anything on the origin.
|
|
114
|
-
*/
|
|
115
|
-
deviceSecret?: string;
|
|
116
|
-
/**
|
|
117
|
-
* The author device's signer, in place of {@link WarrantInput.deviceSecret}:
|
|
118
|
-
* for a key that cannot be exported to hex (a non-extractable WebCrypto key,
|
|
119
|
-
* a passkey, a hardware or remote signer).
|
|
120
|
-
*/
|
|
121
|
-
signer?: Signer;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* The commitment a warrant carries in place of the intent itself.
|
|
125
|
-
*
|
|
126
|
-
* Its own domain, distinct from the signing one, so a value computed for one
|
|
127
|
-
* purpose cannot be presented for the other.
|
|
128
|
-
*/
|
|
129
|
-
export declare function intentHash(method: string, argsJson: unknown): Promise<Uint8Array>;
|
|
130
|
-
/**
|
|
131
|
-
* Sign a warrant and return it hex-encoded, ready for `performIntent`.
|
|
132
|
-
*
|
|
133
|
-
* Returns the encoding rather than an object because the signature covers
|
|
134
|
-
* exactly these bytes: a caller that rebuilt the fields from JSON would have a
|
|
135
|
-
* second spelling able to disagree with what was signed.
|
|
136
|
-
*/
|
|
137
|
-
export declare function signWarrant(input: WarrantInput): Promise<string>;
|
|
138
|
-
/**
|
|
139
|
-
* Decode a cited-head list, refusing one longer than a node will accept.
|
|
140
|
-
* Shared with `creation-warrant.ts`; internal, not exported from the package root.
|
|
141
|
-
*/
|
|
142
|
-
/**
|
|
143
|
-
* A signer's output, refused unless it is a 64-byte Ed25519 signature.
|
|
144
|
-
*
|
|
145
|
-
* The signature is spliced into the encoding at a fixed width, so a signer that
|
|
146
|
-
* returned anything else would yield a warrant that parses as garbage at a
|
|
147
|
-
* relay, far from the signer at fault. Internal.
|
|
148
|
-
*/
|
|
149
|
-
export declare function checkedSignature(signature: Uint8Array): Uint8Array;
|
|
150
|
-
/** A release version as UTF-8, refused past what a node accepts. Shared with `RelayClient`; internal. */
|
|
151
|
-
export declare function releaseVersionBytes(version: string): Uint8Array;
|
|
152
|
-
export declare function citedHeads(heads: string[] | undefined, label: string): Uint8Array[];
|
|
153
|
-
/** A warrant's fields, as hex, decoded from the wire encoding. */
|
|
154
|
-
export interface WarrantFields {
|
|
155
|
-
context: string;
|
|
156
|
-
authorAccount: string;
|
|
157
|
-
/** The author device key `signWarrant` derived from the secret. */
|
|
158
|
-
deviceKey: string;
|
|
159
|
-
executor: string;
|
|
160
|
-
executorKey: string;
|
|
161
|
-
releaseBytecodeId: string;
|
|
162
|
-
/** UTF-8, as hex like `method`. */
|
|
163
|
-
releaseVersion: string;
|
|
164
|
-
method: string;
|
|
165
|
-
intentHash: string;
|
|
166
|
-
accountHeads: string[];
|
|
167
|
-
governanceFloor: string[];
|
|
168
|
-
/** u64 little-endian, as it sits on the wire. */
|
|
169
|
-
nonce: string;
|
|
170
|
-
/** u64 little-endian, as it sits on the wire. */
|
|
171
|
-
notAfter: string;
|
|
172
|
-
signature: string;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Decode a hex warrant back into its fields.
|
|
176
|
-
*
|
|
177
|
-
* Exists because v2's offsets are no longer constants: `method` is a string and
|
|
178
|
-
* the head lists are vectors, so everything after `releaseBytecodeId` moves with
|
|
179
|
-
* the release version's and the method's lengths. Anything reading a warrant by
|
|
180
|
-
* fixed offset - a test, a client inspecting one it was handed - silently reads
|
|
181
|
-
* the wrong field instead of failing, which is how the three call sites that did
|
|
182
|
-
* so passed while comparing the method's length prefix to a nonce.
|
|
183
|
-
*
|
|
184
|
-
* One copy of the layout, beside the encoder that produces it, so the two cannot
|
|
185
|
-
* drift. Internal: not exported from the package root, because a client that
|
|
186
|
-
* needs a warrant's fields has the input it signed.
|
|
187
|
-
*/
|
|
188
|
-
export declare function parseWarrant(warrant: string): WarrantFields;
|
package/dist/warrant/warrant.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mint a warrant: the author's consent for one relay to run one intent, once.
|
|
3
|
-
*
|
|
4
|
-
* This exists because the case delegated authorship is *for* is an account that
|
|
5
|
-
* holds no node — a browser tab, a Node service, a bot. Every one of those is a
|
|
6
|
-
* JS runtime, and until now the only thing that could mint a warrant was `merod`,
|
|
7
|
-
* so "a member with no node" in practice meant "a member with a node it is not
|
|
8
|
-
* allowed to use for anything else".
|
|
9
|
-
*
|
|
10
|
-
* **No dependencies, and that is not incidental.** The primitives are all built
|
|
11
|
-
* in: `crypto.subtle.digest('SHA-256')` for the hash, `crypto.subtle` Ed25519
|
|
12
|
-
* for the signature, and `DataView` for the integers.
|
|
13
|
-
*
|
|
14
|
-
* Warrant v2 (core#3933) gave up the fixed-width encoding that used to make this
|
|
15
|
-
* pure concatenation: `method` travels in the clear as a string and each cited
|
|
16
|
-
* head list is a vector, so three borsh `u32` counts now sit inside the bytes.
|
|
17
|
-
* Two widths to keep straight, and they are not the same: the counts **inside
|
|
18
|
-
* the encoding** are `u32`, while the counts the **signing preimage** hashes are
|
|
19
|
-
* `u64`. Same numbers, different serialization — reusing one for the other
|
|
20
|
-
* produces a warrant that is well-formed and verifies nowhere.
|
|
21
|
-
*
|
|
22
|
-
* **The byte contract is pinned in core**, at
|
|
23
|
-
* `crates/account/src/tests/warrant_wire_fixture.rs`. The domain strings below
|
|
24
|
-
* are `pub(crate)` there, so nothing in that repository forces anyone to notice
|
|
25
|
-
* this file depends on them — which is exactly why the fixture lives on that side
|
|
26
|
-
* and this module's test asserts the same vectors. A drift would otherwise leave
|
|
27
|
-
* this producing well-formed warrants whose signatures verify nowhere, surfacing
|
|
28
|
-
* at a relay as a 403: an authorization refusal, nowhere near the cause.
|
|
29
|
-
*/
|
|
30
|
-
import { concat, domainHash, fromHex, hex, u32le, u64le } from '../crypto/internal.js';
|
|
31
|
-
import { resolveSigner } from '../signer/signer.js';
|
|
32
|
-
const SIGN_DOMAIN = new TextEncoder().encode('calimero.warrant.v2');
|
|
33
|
-
const INTENT_DOMAIN = new TextEncoder().encode('calimero.warrant.intent.v1');
|
|
34
|
-
/**
|
|
35
|
-
* core's `MAX_WARRANT_CITED_HEADS`. A node refuses more than this before doing
|
|
36
|
-
* any Ed25519 work, so minting past it produces a warrant nothing will spend.
|
|
37
|
-
*/
|
|
38
|
-
const MAX_CITED_HEADS = 64;
|
|
39
|
-
/** core's `MAX_WARRANT_RELEASE_VERSION_LEN`, in UTF-8 bytes; a node refuses a longer one before any signature work. */
|
|
40
|
-
const MAX_RELEASE_VERSION_LEN = 256;
|
|
41
|
-
/**
|
|
42
|
-
* The commitment a warrant carries in place of the intent itself.
|
|
43
|
-
*
|
|
44
|
-
* Its own domain, distinct from the signing one, so a value computed for one
|
|
45
|
-
* purpose cannot be presented for the other.
|
|
46
|
-
*/
|
|
47
|
-
export async function intentHash(method, argsJson) {
|
|
48
|
-
const args = new TextEncoder().encode(JSON.stringify(argsJson));
|
|
49
|
-
return domainHash(INTENT_DOMAIN, [new TextEncoder().encode(method), args]);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Sign a warrant and return it hex-encoded, ready for `performIntent`.
|
|
53
|
-
*
|
|
54
|
-
* Returns the encoding rather than an object because the signature covers
|
|
55
|
-
* exactly these bytes: a caller that rebuilt the fields from JSON would have a
|
|
56
|
-
* second spelling able to disagree with what was signed.
|
|
57
|
-
*/
|
|
58
|
-
export async function signWarrant(input) {
|
|
59
|
-
const context = fromHex(input.context, 'context', 32);
|
|
60
|
-
const authorAccount = fromHex(input.authorAccount, 'authorAccount', 32);
|
|
61
|
-
const executor = fromHex(input.executor, 'executor', 32);
|
|
62
|
-
const executorKey = fromHex(input.executorKey, 'executorKey', 32);
|
|
63
|
-
const releaseBytecodeId = fromHex(input.releaseBytecodeId, 'releaseBytecodeId', 32);
|
|
64
|
-
const releaseVersion = releaseVersionBytes(input.releaseVersion ?? '');
|
|
65
|
-
const accountHeads = citedHeads(input.accountHeads, 'accountHeads');
|
|
66
|
-
const governanceFloor = citedHeads(input.governanceFloor, 'governanceFloor');
|
|
67
|
-
const signer = await resolveSigner(input.deviceSecret, input.signer, 'deviceSecret');
|
|
68
|
-
const publicKey = fromHex(signer.publicKey, 'signer.publicKey', 32);
|
|
69
|
-
const method = new TextEncoder().encode(input.method);
|
|
70
|
-
const commitment = await intentHash(input.method, input.argsJson);
|
|
71
|
-
const nonce = u64le(input.nonce);
|
|
72
|
-
const notAfter = u64le(input.notAfter);
|
|
73
|
-
// Each head list is preceded by its own count. `domainHash` length-prefixes
|
|
74
|
-
// every part it is given, so the heads are individually unambiguous — but the
|
|
75
|
-
// two lists are adjacent, so without the counts `accountHeads = [a, b],
|
|
76
|
-
// governanceFloor = []` hashes identically to `[a], [b]`, and a relay could
|
|
77
|
-
// relabel which plane a head was cited from. u64 HERE; u32 in the encoding.
|
|
78
|
-
const preimage = await domainHash(SIGN_DOMAIN, [
|
|
79
|
-
context,
|
|
80
|
-
authorAccount,
|
|
81
|
-
publicKey,
|
|
82
|
-
executor,
|
|
83
|
-
executorKey,
|
|
84
|
-
releaseBytecodeId,
|
|
85
|
-
releaseVersion,
|
|
86
|
-
method,
|
|
87
|
-
commitment,
|
|
88
|
-
u64le(accountHeads.length),
|
|
89
|
-
...accountHeads,
|
|
90
|
-
u64le(governanceFloor.length),
|
|
91
|
-
...governanceFloor,
|
|
92
|
-
nonce,
|
|
93
|
-
notAfter,
|
|
94
|
-
]);
|
|
95
|
-
const signature = checkedSignature(await signer.sign(preimage));
|
|
96
|
-
// Borsh: a `String` is a u32 length then its UTF-8 bytes; a `Vec<[u8; 32]>` is
|
|
97
|
-
// a u32 count then the elements. u32 HERE; u64 in the preimage above.
|
|
98
|
-
return hex(concat(context, authorAccount, publicKey, executor, executorKey, releaseBytecodeId, u32le(releaseVersion.length), releaseVersion, u32le(method.length), method, commitment, u32le(accountHeads.length), ...accountHeads, u32le(governanceFloor.length), ...governanceFloor, nonce, notAfter, signature));
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Decode a cited-head list, refusing one longer than a node will accept.
|
|
102
|
-
* Shared with `creation-warrant.ts`; internal, not exported from the package root.
|
|
103
|
-
*/
|
|
104
|
-
/**
|
|
105
|
-
* A signer's output, refused unless it is a 64-byte Ed25519 signature.
|
|
106
|
-
*
|
|
107
|
-
* The signature is spliced into the encoding at a fixed width, so a signer that
|
|
108
|
-
* returned anything else would yield a warrant that parses as garbage at a
|
|
109
|
-
* relay, far from the signer at fault. Internal.
|
|
110
|
-
*/
|
|
111
|
-
export function checkedSignature(signature) {
|
|
112
|
-
if (!(signature instanceof Uint8Array) || signature.length !== 64) {
|
|
113
|
-
const got = signature instanceof Uint8Array ? `${signature.length} bytes` : typeof signature;
|
|
114
|
-
throw new Error(`signer returned ${got}, expected a 64-byte Ed25519 signature`);
|
|
115
|
-
}
|
|
116
|
-
return signature;
|
|
117
|
-
}
|
|
118
|
-
/** A release version as UTF-8, refused past what a node accepts. Shared with `RelayClient`; internal. */
|
|
119
|
-
export function releaseVersionBytes(version) {
|
|
120
|
-
const bytes = new TextEncoder().encode(version);
|
|
121
|
-
checkReleaseVersionLength(bytes.length);
|
|
122
|
-
return bytes;
|
|
123
|
-
}
|
|
124
|
-
function checkReleaseVersionLength(len) {
|
|
125
|
-
if (len > MAX_RELEASE_VERSION_LEN) {
|
|
126
|
-
throw new Error(`releaseVersion is ${len} bytes, over the ${MAX_RELEASE_VERSION_LEN} a node accepts`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
export function citedHeads(heads, label) {
|
|
130
|
-
const list = heads ?? [];
|
|
131
|
-
if (list.length > MAX_CITED_HEADS) {
|
|
132
|
-
throw new Error(`${label} cites ${list.length} heads, over the ${MAX_CITED_HEADS} a node accepts`);
|
|
133
|
-
}
|
|
134
|
-
return list.map((head, i) => fromHex(head, `${label}[${i}]`, 32));
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Decode a hex warrant back into its fields.
|
|
138
|
-
*
|
|
139
|
-
* Exists because v2's offsets are no longer constants: `method` is a string and
|
|
140
|
-
* the head lists are vectors, so everything after `releaseBytecodeId` moves with
|
|
141
|
-
* the release version's and the method's lengths. Anything reading a warrant by
|
|
142
|
-
* fixed offset - a test, a client inspecting one it was handed - silently reads
|
|
143
|
-
* the wrong field instead of failing, which is how the three call sites that did
|
|
144
|
-
* so passed while comparing the method's length prefix to a nonce.
|
|
145
|
-
*
|
|
146
|
-
* One copy of the layout, beside the encoder that produces it, so the two cannot
|
|
147
|
-
* drift. Internal: not exported from the package root, because a client that
|
|
148
|
-
* needs a warrant's fields has the input it signed.
|
|
149
|
-
*/
|
|
150
|
-
export function parseWarrant(warrant) {
|
|
151
|
-
const at = (start, bytes) => warrant.slice(start * 2, (start + bytes) * 2);
|
|
152
|
-
// A borsh u32 is little-endian, so the hex pairs read back to front.
|
|
153
|
-
const u32At = (start) => Number.parseInt((at(start, 4).match(/../g) ?? []).reverse().join(''), 16);
|
|
154
|
-
let o = 0;
|
|
155
|
-
const take = (bytes) => {
|
|
156
|
-
const field = at(o, bytes);
|
|
157
|
-
o += bytes;
|
|
158
|
-
return field;
|
|
159
|
-
};
|
|
160
|
-
const context = take(32);
|
|
161
|
-
const authorAccount = take(32);
|
|
162
|
-
const deviceKey = take(32);
|
|
163
|
-
const executor = take(32);
|
|
164
|
-
const executorKey = take(32);
|
|
165
|
-
const releaseBytecodeId = take(32);
|
|
166
|
-
const releaseVersionLen = u32At(o);
|
|
167
|
-
o += 4;
|
|
168
|
-
checkReleaseVersionLength(releaseVersionLen);
|
|
169
|
-
const releaseVersion = take(releaseVersionLen);
|
|
170
|
-
const methodLen = u32At(o);
|
|
171
|
-
o += 4;
|
|
172
|
-
const method = take(methodLen);
|
|
173
|
-
const intentHashField = take(32);
|
|
174
|
-
const accountCount = u32At(o);
|
|
175
|
-
o += 4;
|
|
176
|
-
const accountHeads = Array.from({ length: accountCount }, () => take(32));
|
|
177
|
-
const governanceCount = u32At(o);
|
|
178
|
-
o += 4;
|
|
179
|
-
const governanceFloor = Array.from({ length: governanceCount }, () => take(32));
|
|
180
|
-
const nonce = take(8);
|
|
181
|
-
const notAfter = take(8);
|
|
182
|
-
const signature = take(64);
|
|
183
|
-
if (o * 2 !== warrant.length) {
|
|
184
|
-
throw new Error(`warrant is ${warrant.length / 2} bytes but its fields account for ${o}`);
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
context,
|
|
188
|
-
authorAccount,
|
|
189
|
-
deviceKey,
|
|
190
|
-
executor,
|
|
191
|
-
executorKey,
|
|
192
|
-
releaseBytecodeId,
|
|
193
|
-
releaseVersion,
|
|
194
|
-
method,
|
|
195
|
-
intentHash: intentHashField,
|
|
196
|
-
accountHeads,
|
|
197
|
-
governanceFloor,
|
|
198
|
-
nonce,
|
|
199
|
-
notAfter,
|
|
200
|
-
signature,
|
|
201
|
-
};
|
|
202
|
-
}
|