@ftptech/canton-agent-wallet 0.7.3 → 0.8.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/canton-hash.d.ts +8 -57
- package/dist/canton-hash.d.ts.map +1 -1
- package/dist/canton-hash.js +8 -104
- package/dist/canton-hash.js.map +1 -1
- package/dist/cli.js +24 -2
- package/dist/cli.js.map +1 -1
- package/dist/relay-client.d.ts +17 -0
- package/dist/relay-client.d.ts.map +1 -1
- package/dist/relay-client.js.map +1 -1
- package/dist/relay-signer.d.ts +5 -0
- package/dist/relay-signer.d.ts.map +1 -1
- package/dist/relay-signer.js +19 -0
- package/dist/relay-signer.js.map +1 -1
- package/dist/tx.d.ts +21 -1
- package/dist/tx.d.ts.map +1 -1
- package/dist/tx.js +19 -0
- package/dist/tx.js.map +1 -1
- package/dist/verify-prepared.d.ts +2 -248
- package/dist/verify-prepared.d.ts.map +1 -1
- package/dist/verify-prepared.js +5 -1985
- package/dist/verify-prepared.js.map +1 -1
- package/package.json +4 -3
|
@@ -1,250 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
chosenValue: Uint8Array;
|
|
4
|
-
/** The exercised contract id (Exercise.contract_id, field 2), UTF-8 decoded.
|
|
5
|
-
* Pinned by the arms ONLY when the caller supplies an expected value
|
|
6
|
-
* (defense-in-depth: closes the resolve→prepare TOCTOU where a relay points
|
|
7
|
-
* the choice at a contract other than the one it resolved to the agent).
|
|
8
|
-
* undefined if the node carried no decodable contract_id. */
|
|
9
|
-
contractId?: string | undefined;
|
|
10
|
-
/** The exercised contract's template, as a `module:entity` qualified name (the
|
|
11
|
-
* package id is dropped because it changes across package upgrades). Used to
|
|
12
|
-
* pin WHICH template the validated choice runs against, so a relay cannot
|
|
13
|
-
* point a same-named choice at a confusable/attacker template. undefined if
|
|
14
|
-
* the node carried no decodable template_id. */
|
|
15
|
-
templateQualifiedName?: string | undefined;
|
|
16
|
-
}
|
|
17
|
-
/** The recognized `transaction.v1.Node` node-type oneof members. Anything not
|
|
18
|
-
* in this set is treated as PRESENT-AND-UNVALIDATED (reject), never skipped. */
|
|
19
|
-
type NodeKind = "create" | "fetch" | "exercise" | "rollback" | "query_by_key";
|
|
20
|
-
/**
|
|
21
|
-
* One fully-accounted node of the prepared transaction. EVERY entry in
|
|
22
|
-
* DamlTransaction.nodes produces exactly one of these — a node we cannot fully
|
|
23
|
-
* recognize is recorded with `recognized=false` rather than silently dropped, so
|
|
24
|
-
* the invariant layer can fail closed on it (this is the fix for the bypass
|
|
25
|
-
* where a sibling node carried under a non-1000 node version, or as a
|
|
26
|
-
* non-exercise node type, was invisible to verify).
|
|
27
|
-
*/
|
|
28
|
-
interface DecodedNode {
|
|
29
|
-
/** DamlTransaction.Node.node_id (the id `roots`/`children` reference). */
|
|
30
|
-
nodeId: string;
|
|
31
|
-
/** False ⇒ the node version was not the known `v1` (1000) oneof member, or it
|
|
32
|
-
* set an unknown/extra versioned_node member. Such a node is opaque: a
|
|
33
|
-
* participant understanding that version could execute it under our signature,
|
|
34
|
-
* so we must refuse to sign. */
|
|
35
|
-
recognizedVersion: boolean;
|
|
36
|
-
/** The v1 node-type oneof member, or undefined if !recognizedVersion or the
|
|
37
|
-
* inner node set no/unknown node_type member. undefined ⇒ reject. */
|
|
38
|
-
kind?: NodeKind | undefined;
|
|
39
|
-
/** For exercise nodes: the choice id + chosen value (money-critical args). */
|
|
40
|
-
exercise?: DecodedExercise | undefined;
|
|
41
|
-
/** For create nodes: the created template's `module:entity` qualified name
|
|
42
|
-
* (package-id dropped — it changes across upgrades) + the raw `Create.argument`
|
|
43
|
-
* Value bytes. Lets an arm pin a NUMERIC (non-party) field of a CONSEQUENCE
|
|
44
|
-
* create — e.g. the `amount` recorded on a created `X402Escrow` — which the
|
|
45
|
-
* party-leaf backstop cannot see. undefined for non-create nodes / a create
|
|
46
|
-
* carrying no decodable template / argument. */
|
|
47
|
-
create?: {
|
|
48
|
-
templateQualifiedName?: string | undefined;
|
|
49
|
-
argument?: Uint8Array | undefined;
|
|
50
|
-
} | undefined;
|
|
51
|
-
/** node_id references this node declares as its children (Exercise.children /
|
|
52
|
-
* Rollback.children). Used to prove reachability from the single root so no
|
|
53
|
-
* orphan (extra-leg) node can hide in DamlTransaction.nodes. */
|
|
54
|
-
children: string[];
|
|
55
|
-
/** Every `Value`-typed payload this node introduces (exercise chosen_value &
|
|
56
|
-
* exercise_result, create argument). The foreign-party backstop scans the
|
|
57
|
-
* UNION of these across ALL nodes, not just the matched root exercise — so a
|
|
58
|
-
* party/recipient introduced by ANY sibling/consequence node surfaces. */
|
|
59
|
-
values: Uint8Array[];
|
|
60
|
-
/** Node-level `repeated string party` fields that are NOT carried inside a
|
|
61
|
-
* Daml `Value` tree: Create.signatories/stakeholders,
|
|
62
|
-
* Exercise.signatories/stakeholders/acting_parties/choice_observers,
|
|
63
|
-
* Fetch.signatories/stakeholders/acting_parties, and a QueryByKey's key
|
|
64
|
-
* maintainers. The foreign-party backstop scans these too, so an attacker
|
|
65
|
-
* party placed as e.g. a consequence contract's stakeholder/observer (a
|
|
66
|
-
* position invisible to the Value-leaf walk) still surfaces. */
|
|
67
|
-
partyMeta: string[];
|
|
68
|
-
}
|
|
69
|
-
interface DecodedPrepared {
|
|
70
|
-
/** `act_as` parties from Metadata.submitter_info (authoritative submitter). */
|
|
71
|
-
actAs: string[];
|
|
72
|
-
/** DamlTransaction.roots — node_ids declared as transaction roots. */
|
|
73
|
-
roots: string[];
|
|
74
|
-
/** EVERY node in DamlTransaction.nodes, fully accounted (recognized or not). */
|
|
75
|
-
nodes: DecodedNode[];
|
|
76
|
-
/** Convenience view: every recognized Exercise node's choice id + chosen value
|
|
77
|
-
* (backwards-compatible with callers that only inspected exercises). */
|
|
78
|
-
exercises: DecodedExercise[];
|
|
79
|
-
/** Metadata.synchronizer_id (proto field 3, in the SIGNED block). undefined if
|
|
80
|
-
* absent. Pinned to caller intent by the arms when supplied. */
|
|
81
|
-
synchronizerId?: string | undefined;
|
|
82
|
-
/** SIGNED Metadata timing fields, microseconds since the Unix epoch, as
|
|
83
|
-
* BigInts (undefined if absent). All in the proto's "needs to be signed"
|
|
84
|
-
* block, so the agent's signature covers them; the arms sanity-bound them. */
|
|
85
|
-
preparationTime?: bigint | undefined;
|
|
86
|
-
minLedgerEffectiveTime?: bigint | undefined;
|
|
87
|
-
maxLedgerEffectiveTime?: bigint | undefined;
|
|
88
|
-
maxRecordTime?: bigint | undefined;
|
|
89
|
-
/** Every Create `argument` Value carried in Metadata.input_contracts (proto
|
|
90
|
-
* field 7, SIGNED): the authenticated input-contract set. Scanned by the
|
|
91
|
-
* foreign-party backstop so a party that appears ONLY inside an input
|
|
92
|
-
* contract's payload (never in a transaction node) still surfaces. */
|
|
93
|
-
inputContractValues: Uint8Array[];
|
|
94
|
-
/** The node-level `repeated string party` fields of each Metadata.input_contracts
|
|
95
|
-
* Create — signatories (field 6) + stakeholders (field 7) + any contract-key
|
|
96
|
-
* parties (field 8). The V2 metadata hasher binds disclosed/input contracts via
|
|
97
|
-
* `hashNode(toCreateNode)` → addCreateNode, which hashes argument + signatories
|
|
98
|
-
* + stakeholders, so these party fields ARE covered by the agent's signature;
|
|
99
|
-
* the backstop scans them so a foreign party placed ONLY as an input-contract
|
|
100
|
-
* signatory/stakeholder (not in its argument) still surfaces. */
|
|
101
|
-
inputContractPartyMeta: string[];
|
|
102
|
-
/** The SAME authenticated input contracts, GROUPED and carrying identity +
|
|
103
|
-
* template. The flattened lists above are unchanged and remain the generic
|
|
104
|
-
* backstop's input; this grouped view exists so a party occurrence can be
|
|
105
|
-
* related to the contract that explains it (provenance) instead of being
|
|
106
|
-
* accepted by value. */
|
|
107
|
-
inputContracts: DecodedInputContract[];
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Decode a base64 `PreparedTransaction` into the bits we validate.
|
|
111
|
-
*
|
|
112
|
-
* Enumerates EVERY node in DamlTransaction.nodes — every node-version member and
|
|
113
|
-
* every node-type oneof member — recording unrecognized ones as
|
|
114
|
-
* present-but-unvalidated (never silently skipping them). The single allowed
|
|
115
|
-
* root exercise, no-orphan reachability, and the all-nodes value backstop are
|
|
116
|
-
* enforced by the per-arm invariant (`assertSingleAllowedRootExercise`).
|
|
117
|
-
*/
|
|
118
|
-
export declare function decodePrepared(preparedTransactionB64: string): DecodedPrepared;
|
|
119
|
-
/**
|
|
120
|
-
* Party leaves split by WHERE they appear, for the position-aware foreign-party
|
|
121
|
-
* backstop (the fix for the unpinned-admin/dso neutralization bypass):
|
|
122
|
-
* - `rootArg` : party leaves inside the validated root exercise's
|
|
123
|
-
* chosen_value (where the admin/dso legitimately sits, at a
|
|
124
|
-
* known field position).
|
|
125
|
-
* - `elsewhere` : EVERY other party occurrence anywhere else in the signed
|
|
126
|
-
* message — sibling/consequence node Value payloads, the
|
|
127
|
-
* node-level `repeated string party` metadata
|
|
128
|
-
* (signatories/stakeholders/acting_parties/choice_observers/
|
|
129
|
-
* key-maintainers) of ANY node, and the Create arguments of
|
|
130
|
-
* Metadata.input_contracts. The admin/dso is NOT auto-allowed
|
|
131
|
-
* here just because it equals the relay-supplied value at its
|
|
132
|
-
* root position (that value-global exemption is exactly the
|
|
133
|
-
* neutralization hole); it is allowed here ONLY if pinned to
|
|
134
|
-
* an independently-trusted value. Without a trusted pin a
|
|
135
|
-
* relay-controlled admin/dso appearing here is FOREIGN (fail
|
|
136
|
-
* closed) — a value-moving caller must plumb a trusted pin.
|
|
137
|
-
*/
|
|
138
|
-
/** One authenticated `Metadata.input_contracts` entry, grouped (see
|
|
139
|
-
* {@link DecodedPrepared.inputContracts}). */
|
|
140
|
-
export interface DecodedInputContract {
|
|
141
|
-
contractId?: string;
|
|
142
|
-
templateQualifiedName?: string;
|
|
143
|
-
argument?: Uint8Array;
|
|
144
|
-
signatories: string[];
|
|
145
|
-
stakeholders: string[];
|
|
146
|
-
}
|
|
147
|
-
interface ExtractedTransfer {
|
|
148
|
-
sender: string;
|
|
149
|
-
receiver: string;
|
|
150
|
-
amount: string;
|
|
151
|
-
instrumentAdmin: string;
|
|
152
|
-
instrumentId: string;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Extract the transfer body (sender/receiver/amount/instrument) from a
|
|
156
|
-
* TransferFactory_Transfer choice argument, BY TYPE at its structural position.
|
|
157
|
-
*/
|
|
158
|
-
export declare function extractTransfer(chosenValue: Uint8Array): ExtractedTransfer;
|
|
159
|
-
export interface PreparedTransferExpectation {
|
|
160
|
-
/** The agent's own party — must be the transfer sender (caller intent). */
|
|
161
|
-
sender: string;
|
|
162
|
-
/** The intended recipient (merchant payTo / withdraw target) — caller intent. */
|
|
163
|
-
receiver: string;
|
|
164
|
-
/** The EXACT amount string the agent asked to transfer — caller intent. */
|
|
165
|
-
amount: string;
|
|
166
|
-
/**
|
|
167
|
-
* The instrument id the agent expects (e.g. "Amulet"). Caller intent — the
|
|
168
|
-
* agent chooses what asset to pay in. The instrument ADMIN (DSO party) is
|
|
169
|
-
* resolved by the relay and is deliberately NOT used as a trust anchor: we
|
|
170
|
-
* never whitelist a relay-supplied party. Pass it only if you have an
|
|
171
|
-
* independently-trusted value to additionally pin.
|
|
172
|
-
*/
|
|
173
|
-
instrumentId: string;
|
|
174
|
-
/** Optional, independently-trusted instrument admin (the DSO party) to pin. When
|
|
175
|
-
* supplied it is pinned by exact equality AND becomes the ONLY admin/dso value
|
|
176
|
-
* the foreign-party backstop excludes outside its root position — closing the
|
|
177
|
-
* "alias the unpinned admin to the attacker and inject it as a consequence
|
|
178
|
-
* recipient" neutralization. Pass it whenever you have an out-of-band DSO. */
|
|
179
|
-
instrumentAdmin?: string;
|
|
180
|
-
/** Optional, caller-intent synchronizer id (the merchant-advertised domain).
|
|
181
|
-
* When supplied, the SIGNED Metadata.synchronizer_id is pinned to it (fail-
|
|
182
|
-
* closed if absent/different) so a relay cannot land the agent's signature on a
|
|
183
|
-
* domain of its choosing. */
|
|
184
|
-
synchronizerId?: string;
|
|
185
|
-
/** Optional, caller-intent template `module:entity` qualified name of the
|
|
186
|
-
* TransferFactory the choice runs against (e.g. the token-standard transfer
|
|
187
|
-
* factory). When supplied, the exercise's template_id is pinned, closing the
|
|
188
|
-
* template/contract-confusion surface. */
|
|
189
|
-
templateQualifiedName?: string;
|
|
190
|
-
/** Optional, caller-intent contract id of the factory the exercise targets.
|
|
191
|
-
* When supplied, Exercise.contract_id is pinned by exact equality (fail-closed
|
|
192
|
-
* on divergence) — defense-in-depth closing the resolve→prepare TOCTOU. No-op
|
|
193
|
-
* when omitted (the all-nodes party backstop still contains any redirect). */
|
|
194
|
-
expectedContractId?: string;
|
|
195
|
-
/** Inject Date.now() for testability of the timing sanity checks. */
|
|
196
|
-
nowMs?: number;
|
|
197
|
-
}
|
|
198
|
-
export declare class PreparedDecodeError extends Error {
|
|
199
|
-
constructor(message: string);
|
|
200
|
-
}
|
|
201
|
-
export declare class PreparedTransferMismatchError extends Error {
|
|
202
|
-
constructor(message: string);
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Canonicalize a CC amount decimal string to a fixed 10-fractional-digit form
|
|
206
|
-
* for VALUE-equality comparison. Canton encodes amounts at 10 decimals
|
|
207
|
-
* ("0.0200000000"); a caller (e.g. `withdraw --amount 0.02`) may pass a short
|
|
208
|
-
* form, so comparing the raw strings would spuriously fail verify-before-sign
|
|
209
|
-
* even though the numeric value is identical. Pure string math (no float) so
|
|
210
|
-
* large amounts keep full precision. A non-numeric input is returned as-is, so
|
|
211
|
-
* it still mismatches (fail-closed).
|
|
212
|
-
*
|
|
213
|
-
* UNIT-BY-SCHEME invariant: BOTH sides of every amount compare here are
|
|
214
|
-
* on-ledger Daml **Decimals** — `t.amount` is decoded from the prepared tx's
|
|
215
|
-
* `Value.numeric` (always a ledger Decimal) and `expect.amount` is the caller's
|
|
216
|
-
* INTENDED ledger Decimal (the relay/agent path sources `opts.amount` as the
|
|
217
|
-
* Decimal, never the x402 wire atomic value). `canonicalAmount` therefore only
|
|
218
|
-
* pads Decimals; it must NEVER be fed an atomic integer (an atomic "1" would
|
|
219
|
-
* canonicalize to "1.0000000000" and silently mis-compare against the ledger
|
|
220
|
-
* "0.0000000001"). If a future caller ever sources `expect.amount` from an
|
|
221
|
-
* atomic-scheme wire, it MUST first convert via
|
|
222
|
-
* `wireAmountToLedgerDecimal(scheme, amount)` from @ftptech/x402-canton-core.
|
|
223
|
-
*/
|
|
224
|
-
export declare function canonicalAmount(raw: string): string;
|
|
225
|
-
export declare function assertPreparedTransferMatches(preparedTransactionB64: string, expect: PreparedTransferExpectation): void;
|
|
226
|
-
/** Caller-intent expectation for a `TransferInstruction_Accept` (claim) prepare. */
|
|
227
|
-
export interface PreparedAcceptExpectation {
|
|
228
|
-
/** The agent's own party — must be the authoritative submitter (act_as). */
|
|
229
|
-
selfParty: string;
|
|
230
|
-
/** Inject Date.now() for testability of the timing sanity checks. */
|
|
231
|
-
nowMs?: number;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Assert the relay-returned `preparedTransaction` for the claim path encodes
|
|
235
|
-
* EXACTLY a single `TransferInstruction_Accept` exercise submitted by the agent —
|
|
236
|
-
* and is NOT an outbound transfer/create-transfer-command drain. Throws
|
|
237
|
-
* `PreparedTransferMismatchError` on any mismatch and `PreparedDecodeError` if
|
|
238
|
-
* the bytes are not a decodable PreparedTransaction. Call BEFORE signing the
|
|
239
|
-
* hash. Fail-closed: anything not positively proven to be an inbound accept by
|
|
240
|
-
* the agent throws.
|
|
241
|
-
*/
|
|
242
|
-
export declare function assertPreparedAcceptMatches(preparedTransactionB64: string, expect: PreparedAcceptExpectation): void;
|
|
243
|
-
/**
|
|
244
|
-
* How to bind the relay-returned `hash` to the `preparedTransaction` bytes the
|
|
245
|
-
* agent just structurally validated. Exactly one of these must hold for a
|
|
246
|
-
* value-moving transfer, or we refuse to sign (fail-closed).
|
|
247
|
-
*/
|
|
1
|
+
export { decodePrepared, extractTransfer, canonicalAmount, assertPreparedTransferMatches, assertPreparedAcceptMatches, PreparedDecodeError, PreparedTransferMismatchError, } from "@ftptech/x402-canton-core";
|
|
2
|
+
export type { DecodedPrepared, DecodedNode, DecodedExercise, DecodedInputContract, PreparedTransferExpectation, PreparedAcceptExpectation, } from "@ftptech/x402-canton-core";
|
|
248
3
|
export interface HashBindingOptions {
|
|
249
4
|
/**
|
|
250
5
|
* A function that recomputes the signing hash from the prepared-transaction
|
|
@@ -366,5 +121,4 @@ export declare function assertOnboardingTopologyBindsKey(onboardingTransactionsB
|
|
|
366
121
|
* which #1 already prevents). Field-level pinning is the hardening follow-up.
|
|
367
122
|
*/
|
|
368
123
|
export declare function assertPreparedSelfPreapproval(preparedTransactionB64: string, party: string): void;
|
|
369
|
-
export {};
|
|
370
124
|
//# sourceMappingURL=verify-prepared.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-prepared.d.ts","sourceRoot":"","sources":["../src/verify-prepared.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"verify-prepared.d.ts","sourceRoot":"","sources":["../src/verify-prepared.ts"],"names":[],"mappings":"AA+GA,OAAO,EACL,cAAc,EACd,eAAe,EACf,eAAe,EACf,6BAA6B,EAC7B,2BAA2B,EAC3B,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,eAAe,EACf,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,2BAA2B,EAC3B,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AAMnC,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,aAAa,CAAC,EAAE,CAAC,sBAAsB,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAC7E;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,iFAAiF;AACjF,qBAAa,4BAA6B,SAAQ,KAAK;gBACzC,OAAO,EAAE,MAAM;CAI5B;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,iBAAiB,CACrC,sBAAsB,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA4Df;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,sBAAsB,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,GACd,OAAO,CAIT;AAoDD;6EAC6E;AAC7E,qBAAa,+BAAgC,SAAQ,KAAK;gBAC5C,OAAO,EAAE,MAAM;CAI5B;AAED,uEAAuE;AACvE,MAAM,WAAW,6BAA6B;IAC5C,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AA+BD;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,yBAAyB,EAAE,MAAM,EAAE,EACnC,MAAM,EAAE,6BAA6B,GACpC,IAAI,CA0EN;AAkND;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,6BAA6B,CAC3C,sBAAsB,EAAE,MAAM,EAC9B,KAAK,EAAE,MAAM,GACZ,IAAI,CAwBN"}
|