1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//! EIP-712 `BindNodeId` verification for ephemeral client bindings.
//!
//! Clients without on-chain registration MAY attach a signed
//! `NodeId`↔Ethereum-address binding to their first `StreamRequest` on a
//! connection (ADR 003 §Off-Chain Ephemeral Binding, ADR 005 §Client identity
//! binding). The delivering node verifies the EIP-712 signature, recovers the
//! Ethereum address, and uses it for voucher attribution for the connection's
//! lifetime. The binding is not stored on-chain.
//!
//! The signed payload follows ADR 003 §Binding Message Format exactly so an
//! off-chain Rust verification byte-matches what the on-chain `CapacityBond`
//! contract accepts:
//!
//! ```text
//! BindNodeId(bytes32 nodeId,uint64 nonce)
//! ```
//!
//! Ephemeral client bindings always sign `nonce = 0` (the on-chain registration
//! path uses the per-address `bindingNonce`, a distinct domain of values).
//!
//! # Domain
//!
//! The EIP-712 domain is the `CapacityBond` contract deployment
//! (`EIP712("CapacityBond", "1")`, see `contracts/src/CapacityBond.sol`), so an
//! ephemeral binding is bound to the same chain + contract as on-chain
//! registration, preventing cross-chain / cross-contract replay (ADR 003
//! §Binding Message Format).
//!
//! # Scope
//!
//! Only EOA (`ecrecover`) verification is implemented here, matching the
//! EOA-only off-chain signing stance of [`crate::probe_sig`] and
//! [`crate::voucher`]. Off-chain verification is EOA recovery only (ADR 024
//! §Off-Chain Signature Verification — EOA Recovery Only); a smart-account
//! client binds through a capability-delegated EOA `signer` instead, so no
//! ERC-1271 off-chain branch exists.
use Eip712Domain;
use ;
use ;
/// EIP-712 domain `name` field for the `CapacityBond` deployment. Must match
/// `EIP712("CapacityBond", "1")` in `contracts/src/CapacityBond.sol` — a
/// mismatch produces a different digest and every binding fails to recover the
/// expected address.
pub const CAPACITY_BOND_DOMAIN_NAME: &str = "CapacityBond";
/// EIP-712 domain `version` field.
pub const CAPACITY_BOND_DOMAIN_VERSION: &str = "1";
/// `nonce` value for ephemeral (off-chain) client bindings (ADR 003 §Off-Chain
/// Ephemeral Binding). On-chain registration uses the per-address
/// `bindingNonce` instead.
pub const EPHEMERAL_BINDING_NONCE: u64 = 0;
// Solidity struct mirroring ADR 003 §Binding Message Format. The `sol!` macro
// uses the Rust struct name as the on-chain Solidity type name; the contract's
// struct is `BindNodeId`, wrapped in a private module to keep the name local.
use BindNodeId as BindNodeIdSol;
use RegisterNode as RegisterNodeSol;
/// Construct the EIP-712 domain used to verify `BindNodeId` signatures for a
/// given `CapacityBond` deployment.
/// EIP-712 signing hash for a `BindNodeId(nodeId, nonce)` under `domain`.
///
/// Used for the ephemeral client binding (ADR 003 §Off-Chain Ephemeral Binding)
/// and on-chain rebinding (`bindNodeId`, key rotation). Initial node
/// registration signs [`register_node_signing_hash`] instead.
/// EIP-712 signing hash for a `RegisterNode(nodeId, nonce, termsHash)` under
/// `domain` — the initial on-chain node registration binding (ADR 019 § Terms
/// Acceptance). `terms_hash` MUST equal the contract's `currentTermsHash`, and
/// the signature cryptographically binds the operator's acceptance to it.
/// Verify an ephemeral client binding and recover the attesting Ethereum
/// address (EOA `ecrecover`).
///
/// `node_id` is the client's 32-byte iroh `NodeId` (the connection's
/// authenticated remote id); `nonce` is [`EPHEMERAL_BINDING_NONCE`] for
/// off-chain bindings. The recovered address is what the node uses for voucher
/// attribution; the caller MUST further check it equals the channel's `client`
/// before accepting vouchers.
///
/// # Errors
///
/// - [`BindError::BadLength`] — `signature` is not 65 bytes (the EOA form).
/// - [`BindError::Malformed`] — well-formed length but non-canonical `s` /
/// invalid recovery id, so no address can be recovered.
/// Failure modes for [`verify_binding`].
// signer/signed pair up clearly here