visualsign 0.1.0

Visualsign package defines the structures helper methods to create VisualSign payloads for Anchorage's Open Source VisualSign
Documentation
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# Visual Sign Protocol Documentation
This document provides specifications for the Visual Sign Protocol (VSP), a structured format for displaying transaction details to users for approval. The VSP is designed to present meaningful, human-readable information about operations requiring signatures.

## Important Concepts

### Non-Canonical Format
**The SignablePayload JSON format is NOT canonical.** It should be treated by signers as an **opaque string field**. While we maintain deterministic ordering (currently alphabetical) for debugging consistency and cross-implementation compatibility, this is an implementation detail that may change. Signers should not parse or depend on the specific JSON structure or field ordering.

### Display Requirements for Implementers
Display elements (wallets, signing interfaces) are responsible for:
- **Parsing and interpreting** the SignablePayload to determine what to show users
- **Ensuring all fields are displayed** - Every field in the payload MUST be shown to the user
- **Minimum display guarantee** - At the very least, the `FallbackText` for each field must be displayed
- **Making display decisions** - The display element decides how to render fields (layout, styling, grouping)
- **Respecting user preferences** - Honor accessibility settings and display preferences

**Notes**
* We don't use v1 text field types, but they're around for backwards compatibility for now
* AnnotatedFields are a layer on top of SignablePayload field for our wallet to provide more context, it's not in scope of the SignablePayload, it's still in structs but we'll consider removing in future
* Field ordering is deterministic but not guaranteed to be alphabetical in future versions - see [Deterministic Ordering Documentation]docs/DETERMINISTIC_ORDERING.md

## SignablePayload
A SignablePayload is the core structure that defines what is displayed to the user during the signing process. It contains metadata about the transaction and a collection of fields representing the transaction details.

### Structure
<details> <summary>SignablePayload Structure</summary>

```json
{
  "Version": "0",
  "Title": "Withdraw",
  "Subtitle": "to 0x8a6e30eE13d06311a35f8fa16A950682A9998c71",
  "Fields": [
    {
      "FallbackText": "1 ETH",
      "Label": "Amount",
      "Type": "amount_v2",
      "AmountV2": {
        "Amount": "1",
        "Abbreviation": "ETH"
      }
    },
    ...
  ],
  "EndorsedParamsDigest": "DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
}
```
</details>


### Payload Components

| Field                 | Type                       | Description                               |
|-----------------------|----------------------------|-------------------------------------------|
| Version               | String                     | Protocol version                          |
| Title                 | String                     | Primary title for the operation           |
| Subtitle              | String (optional)          | Secondary descriptive text                |
| PayloadType                | String | Identifier for the SignablePayload (ex: Withdrawal, Swap, etc)|
| Fields                | Array of SignablePayloadField | The fields containing transaction details |
| EndorsedParamsDigest  | String (optional)          | Digest of endorsed parameters             |

## Field Types
The Visual Sign Protocol supports various field types to represent different kinds of data.

#### Common Field Structure
All field types include these common properties:

<details> <summary>Common Field Properties</summary>

```json
{
  "Label": "Amount",
  "FallbackText": "1 ETH",
  "Type": "amount_v2"
}
```
</details>


| Field         | Type   | Description                                |
|---------------|--------|--------------------------------------------|
| Label         | String | Field label shown to the user              |
| FallbackText  | String | Plain text representation (for limited clients) |
| Type          | String | Type identifier for the field              |

### Specific Field Types


#### Text Fields
<details> <summary>Text Field Example</summary>

```json
{
  "Label": "Asset",
  "FallbackText": "ETH | Ethereum",
  "Type": "text_v2",
  "TextV2": {
    "Text": "ETH | Ethereum"
  }
}
```
</details>


#### Address Fields
<details> <summary>Address Field Example</summary>

```json
{
  "Label": "Amount",
  "FallbackText": "0.00001234",
  "Type": "amount_v2",
  "AmountV2": {
    "Amount": "0.00001234",
    "Abbreviation": "BTC"
  }
}
```
</details>

### Amount Fields
Amount fields are user friendly ways to display the value being transferred 
<details> <summary>Amount Field Example</summary>

```json
{
  "Label": "Amount",
  "FallbackText": "0.00001234 BTC",
  "Type": "amount_v2",
  "AmountV2": {
    "Amount": "0.00001234",
    "Abbreviation": "BTC"
  }
}
```
</details>

### Number Fields

<details> <summary>Number Field Example</summary>

```json
{
  "Label": "gasLimit",
  "FallbackText": "21000",
  "Type": "number",
  "Number": {
    "Value": "21000"
  }
}
```
</details>

### Divider Fields

Divider fields are UI elements to split the UI on. This is used for clarity and to allow the UI to keep views in separate pages if needed.

<details> <summary>Divider Field Example</summary>

```json
{
  "Label": "",
  "Type": "divider",
  "Divider": {
    "Style": "thin"
  }
}
```
</details>


### Layout Fields
We have additional layout fields for two different use cases - one for creating preview elements, where a condensed view can be optionally expanded by the user. 

<details> <summary>Preview Layout Field Example</summary>

```json
{
  "Type": "preview_layout",
  "PreviewLayout": {
    "Title": "Delegate",
    "Subtitle": "1 SOL Delegated to Jito4APyf642JPZPx3hGc6WWJ8zPKtRbRs4P815Awbb"
  },
  "Condensed": {
    "Fields": [ /* array of SignablePayloadFields */]
  },
  "Expanded": {
    "Fields": [ /* array of SignablePayloadFields */]
  }
}
```
</details> 


## Endorsed Params

The Endorsed Params feature allows passing additional parameters for the visualizer to interpret and potentially use for transforming the raw transaction to make meaningful display for user in a deterministic way. 

### Structure

Endorsed parameters are cryptographically bound to the SignablePayload through the `EndorsedParamsDigest` field, which contains a hash of all endorsed parameters. These are presented as an example - and may be chain or wallet-specific.

<details> <summary>Endorsed Params Structure</summary>

```json
{
  "EndorsedParams": {
    "ChainId": "1",
    "ContractAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
    "MethodSignature": "transfer(address,uint256)",
    "Nonce": "42",
    "CallData": "0xa9059cbb000000000000000000000000...",
    "ABIs": {},
    "IDLs": {}
  }
}
```
</details>

### Usage

1. **Transaction Construction**: The visualizer service collects all necessary parameters for constructing a valid transaction.

2. **Parameter Separation**: Parameters are separated into:
   - User-facing fields (included in the `Fields` array)
   - Hidden parameters (included in `EndorsedParams`)

3. **Digest Creation**: The service computes a hash of the endorsed parameters:
   ```
   EndorsedParamsDigest = sha256(serialize(EndorsedParams))
   ```

4. **Payload Assembly**: The digest is included in the SignablePayload, cryptographically binding the hidden parameters to the displayed information.

### Security Considerations

- The signer must verify that the `EndorsedParamsDigest` matches the endorsed parameters used for transaction construction
- Parameters that affect user funds or authorization should generally be displayed rather than hidden
- Implementations should document which parameters are endorsed vs. displayed to ensure transparency

### Example Use Cases

- Network fees and gas parameters
- Technical identifiers (contract addresses, chain IDs)
- Implementation-specific parameters (nonces, replay protection values)
- Method signatures and serialized call data


## Example Fixtures
Below are screenshots corresponding to specific fixture examples:

Bitcoin Withdraw
![Bitcoin Withdraw using visualsign](docs/testFixtures.bitcoin_withdraw_fixture_generation.png)

ERC20 Token Withdraw
![ERC20 Token Withdraw](docs/testFixtures.erc20_withdraw.png)

Solana Withdraw with Expandable Preview Layouts
![Solana withdraw main page](docs/testFixtures.solana_withdraw_fixture_generation.png)
Expanding fields, these are expected to be shown when one of the expandable fields is clicked

1. ![Solana details1]docs/testFixtures.solana_withdraw_fixture_generation_expandable_details_1.png
2. ![alt text]docs/testFixtures.solana_withdraw_fixture_generation_expandable_details_2.png


### Implementation Considerations
Field Ordering: Fields should be displayed in the order they appear in the Fields array
Version Compatibility: Clients should check the Version field to ensure they can properly render the payload
Fallback Rendering: If a client doesn't understand a field type, it should fall back to displaying the FallbackText
Security: Implementations should validate the ReplayProtection and EndorsedParamsDigest values


## Extending SignablePayloadField Types

The VisualSign Protocol is designed to be extensible, allowing developers to safely add new field types while maintaining backward compatibility and ensuring data integrity.

### Architecture Overview

The field serialization system uses a **trait-based architecture with compile-time and runtime verification** that provides multiple layers of protection against incomplete implementations:

```rust
trait FieldSerializer {
    fn serialize_to_map(&self) -> Result<BTreeMap<String, Value>, Error>;
    fn get_expected_fields(&self) -> Vec<&'static str>;
}
```

### Key Features

- **โš™๏ธ Compile-Time Enforcement**: `DeterministicOrdering` trait ensures types implement deterministic serialization
- **๐Ÿ”’ Runtime Verification**: Automatically verifies all expected fields are present during serialization
- **๐Ÿ“ Deterministic Ordering**: Fields are automatically sorted deterministically (currently alphabetically) for consistent output
- **๐Ÿšจ Error Detection**: Missing or unexpected fields cause immediate serialization failure with detailed error messages
- **๐Ÿงช Test-Driven**: Comprehensive test suite proves the verification system works correctly
- **๐Ÿ”„ Extensible**: Adding new field types is straightforward and safe

### How to Add New Field Types

#### 1. Define the Field Structure

First, create the data structure for your new field type:

```rust
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct SignablePayloadFieldCurrency {
    #[serde(rename = "CurrencyCode")]
    pub currency_code: String,
    #[serde(rename = "Symbol")]
    pub symbol: String,
    #[serde(rename = "ExchangeRate", skip_serializing_if = "Option::is_none")]
    pub exchange_rate: Option<String>,
}
```

#### 2. Add the Enum Variant

Add your new variant to the `SignablePayloadField` enum:

```rust
pub enum SignablePayloadField {
    // ... existing variants ...

    #[serde(rename = "currency")]
    Currency {
        #[serde(flatten)]
        common: SignablePayloadFieldCommon,
        #[serde(rename = "Currency")]
        currency: SignablePayloadFieldCurrency,
    },
}
```

#### 3. Implement Serialization Logic

Add your field to both required methods in the `FieldSerializer` implementation:

```rust
impl FieldSerializer for SignablePayloadField {
    fn serialize_to_map(&self) -> Result<BTreeMap<String, Value>, Error> {
        let mut fields = HashMap::new();
        match self {
            // ... existing variants ...

            SignablePayloadField::Currency { common, currency } => {
                serialize_field_variant!(fields, "currency", common, ("Currency", currency));
            },
        }
        Ok(fields.into_iter().collect())
    }

    fn get_expected_fields(&self) -> Vec<&'static str> {
        let mut base_fields = vec!["FallbackText", "Label", "Type"];
        match self {
            // ... existing variants ...

            SignablePayloadField::Currency { .. } => base_fields.push("Currency"),
        }
        base_fields.sort();
        base_fields
    }
}
```

#### 4. Update Helper Methods

Add your variant to the existing helper methods:

```rust
impl SignablePayloadField {
    pub fn field_type(&self) -> &str {
        match self {
            // ... existing variants ...
            SignablePayloadField::Currency { .. } => "currency",
        }
    }

    // Update other helper methods as needed...
}
```

#### 5. Implement DeterministicOrdering Trait

**Critical**: Your new field type must implement the `DeterministicOrdering` trait to be usable in contexts requiring deterministic serialization:

```rust
// This is already implemented for SignablePayloadField, but if creating a new top-level type:
impl DeterministicOrdering for YourNewType {}
```

Without this implementation, the type cannot be used in functions requiring deterministic ordering, and compilation will fail with a clear error message.

### Runtime Verification System

The system automatically verifies field completeness during serialization:

```rust
// โœ… Successful serialization - all fields present
let currency_field = SignablePayloadField::Currency {
    common: SignablePayloadFieldCommon {
        fallback_text: "USD ($)".to_string(),
        label: "Payment Currency".to_string(),
    },
    currency: SignablePayloadFieldCurrency {
        currency_code: "USD".to_string(),
        symbol: "$".to_string(),
        exchange_rate: None,
    },
};

let json = serde_json::to_string(&currency_field)?;
// Result: {"Currency":{"CurrencyCode":"USD","Symbol":"$"},"FallbackText":"USD ($)","Label":"Payment Currency","Type":"currency"}
```

If you forget to serialize a field or have mismatched expectations:

```rust
// โŒ This would fail with detailed error message:
// "Missing expected field 'Currency'. Expected: ["Currency", "FallbackText", "Label", "Type"], Actual: ["FallbackText", "Label", "Type"]"
```

### Comprehensive Testing

The system includes extensive tests that prove the verification works:

```rust
#[test]
fn test_new_field_type() {
    // Test that new field type serializes correctly with verification
    let field = SignablePayloadField::Currency { /* ... */ };

    // This will succeed only if ALL expected fields are present and correctly serialized
    let result = serde_json::to_string(&field);
    assert!(result.is_ok());

    // Verify alphabetical ordering
    let value: serde_json::Value = serde_json::from_str(&result.unwrap()).unwrap();
    if let serde_json::Value::Object(map) = value {
        let keys: Vec<_> = map.keys().cloned().collect();
        // Keys are automatically in alphabetical order
        assert_eq!(keys, vec!["Currency", "FallbackText", "Label", "Type"]);
    }
}
```

### Benefits of This Approach

1. **๐Ÿ›ก๏ธ Defense in Depth**:
   - **Compile-time**: Exhaustive pattern matching ensures all variants are handled, `DeterministicOrdering` trait enforces proper implementation
   - **Runtime**: Field verification catches missing/incorrect fields
   - **Test-time**: Comprehensive tests prove the system works

2. **๐Ÿ” Clear Error Messages**:
   - Missing `DeterministicOrdering` trait causes compile-time error with clear message
   - Missing fields are immediately identified with specific field names at runtime
   - Unexpected fields are caught and reported
   - Detailed error context helps debugging

3. **๐Ÿ“Š Consistent Output**:
   - All fields automatically ordered deterministically (currently alphabetically)
   - Consistent JSON structure across all field types
   - Backward compatibility maintained

4. **๐Ÿš€ Easy Extension**:
   - Adding new field types requires minimal code changes
   - Macro-based approach reduces boilerplate
   - Compile-time checking makes it impossible to miss required implementation steps

### Migration from Legacy Approach

The new system maintains full backward compatibility while adding safety:

- All existing field types work unchanged
- JSON output format is identical
- No breaking changes to API
- Existing tests continue to pass

### Best Practices

1. **Always test new field types** with the provided verification tests
2. **Use descriptive field names** that clearly indicate their purpose
3. **Follow the naming convention** of existing field types
4. **Document new field types** in this README
5. **Consider backward compatibility** when designing new field structures

This extensible architecture transforms field extension from a error-prone manual process into a safe, verified, and automatic system that catches mistakes before they can cause issues in production.