Skip to content

Commit 2b74b5f

Browse files
committed
design: p2sh and data scripts
1 parent eb662c7 commit 2b74b5f

File tree

1 file changed

+194
-0
lines changed

1 file changed

+194
-0
lines changed

rfcs/0001-p2sh-and-data-script.md

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# Summary
2+
[summary]: #summary
3+
4+
Add support for P2SH scripts and data scripts on the Hathor Ledger app.
5+
6+
# Motivation
7+
[motivation]: #motivation
8+
9+
P2SH and data scripts are used in some projects that may send large amounts of tokens by the project and by users, so we require added protection for these use-cases by allowing the users to use the Ledger device with P2SH and data scripts outputs.
10+
11+
# Guide-level explanation
12+
[guide-level-explanation]: #guide-level-explanation
13+
14+
When parsing the transaction we will need to decode each element, the current design aims to add some versatility to this decoding.
15+
Currently we only support P2PKH (Pay-To-PubKey-Hash) outputs and to add support for P2SH (Pay-To-Script-Hash) outputs and data output we need to change how we parse, validate and show the output to the user.
16+
17+
## Output scripts
18+
19+
An output has:
20+
- Value: 4 or 8 bytes
21+
- Token data: 1 byte
22+
- Script: variable number of bytes
23+
24+
The script describes how the output will be spent, for instance which address owns the output or in the case of the data output, that it cannot be spent.
25+
The information on the script will be used so the user can confirm the output or deny it, meaning some data will have to be extracted from the script and processed.
26+
27+
---
28+
29+
### P2PKH
30+
31+
The structure is
32+
`OP_DUP(1b) | OP_HASH160(1b) | PUBKEY_HASH_LEN(1b) | PUBKEY_HASH(20b) | OP_EQUALVERIFY(1b) | OP_CHECKSIG(1b)`
33+
34+
Which amounts to 25 bytes, the `PUBKEY_HASH` is the address the output is sending tokens to.
35+
36+
### P2SH
37+
38+
The structure is
39+
`OP_HASH160(1b) | SCRIPT_HASH_LEN(1b) | SCRIPT_HASH(20b) | OP_EQUAL(1b)`
40+
41+
Which amounts to 23 bytes, the `SCRIPT_HASH` is the address the output is sending tokens to.
42+
43+
Obs: P2PKH and P2SH also have a timelock variant which is not supported on the Ledger, but adds 6 bytes to the start of the script with the structure `4(1b) | TIMESTAMP (4b) | OP_CHECKTIMESTAMP (1b)`.
44+
45+
### Data script
46+
47+
The structure of a data script can be either:
48+
- `OP_PUSHDATA1(1b) | DATA_LEN(1b) | DATA(var)`
49+
- For data lengths of $[1, 255]$ but usually used for $(75, 255]$ due to the extra opcode.
50+
- `DATA_LEN(1b) | DATA(var)`
51+
- Only for data lengths of $[1, 75]$
52+
53+
---
54+
55+
## Script Parser
56+
57+
The parser will be changed to identify the type of script and if it is valid extract the data to the global context.
58+
This way of doing the extraction allows the code to read the data, validate and return an error if invalid, otherwise continue safely by copying the required data.
59+
60+
When identifying we should:
61+
- Check if it is a P2PKH script
62+
- Check if it is a P2SH script
63+
- Check if it is a Data script
64+
- Return as Unknown script (not allowed)
65+
66+
## Parsed data
67+
68+
Once the type of script is identified we need to extract, validate then store the data for the user to confirm.
69+
To do this we will change the output structure to allow storing the new information on the global context.
70+
71+
```c
72+
73+
// src/transaction/types.h
74+
75+
/**
76+
* Script types
77+
*/
78+
typedef enum {
79+
SCRIPT_UNKNOWN = 0,
80+
SCRIPT_P2PKH = 1,
81+
SCRIPT_P2SH = 2,
82+
SCRIPT_DATA = 3,
83+
} script_type_t;
84+
85+
/**
86+
* Output script info
87+
*/
88+
typedef struct {
89+
script_type_t type;
90+
uint8_t hash[PUBKEY_HASH_LEN]; // P2SH or P2PKH
91+
uint8_t data_index; // Index on G_context.tx_info.output_datas
92+
} output_script_info_t;
93+
94+
/**
95+
* Data script type alias
96+
*/
97+
typedef uint8_t data_script_t[MAX_DATA_SCRIPT_LEN];
98+
99+
/**
100+
* Structure for transaction output
101+
*/
102+
typedef struct {
103+
uint8_t index;
104+
uint64_t value;
105+
uint8_t token_data;
106+
output_script_info_t script;
107+
} tx_output_t;
108+
109+
// src/types.h
110+
111+
/**
112+
* Structure for transaction information context.
113+
*/
114+
typedef struct {
115+
// ...
116+
token_symbol_t* tokens[TX_MAX_TOKENS];
117+
uint8_t tokens_len;
118+
119+
data_script_t output_datas[TX_MAX_DATA_OUTPUTS];
120+
uint8_t output_datas_len;
121+
} sign_tx_ctx_t;
122+
```
123+
124+
To avoid provisioning more data than necessary the data script bytes will be saved on an array on the global context and the output will only have the index to its data.
125+
This way we can have different values for the max number of outputs and max number of data outputs.
126+
127+
> [!IMPORTANT]
128+
> For Ledger Nano S:
129+
> - MAX_ALLOWED_DATA_OUTPUTS = 1
130+
> - MAX_DATA_SCRIPT_LEN = 53 // Data will go up to 50 bytes
131+
>
132+
> For other devices:
133+
> - MAX_ALLOWED_DATA_OUTPUTS = 4
134+
> - MAX_DATA_SCRIPT_LEN = 153 // Data will go up to 150 bytes
135+
136+
## UI Interaction
137+
### P2SH addresses
138+
139+
This can be done by creating a method to convert script hash to base58 address, which requires the network's p2sh version byte, then display normally on the address step.
140+
141+
```c
142+
/**
143+
* Convert script hash160 to base58 address.
144+
*
145+
* address = version byte + script_hash + first 4 bytes of sha256d(hash)
146+
*
147+
* @param[in] script_hash
148+
* Script hash160
149+
* An array of at least 20 bytes (uint8_t).
150+
* @param[in] script_hash_len
151+
* Length of script_hash buffer
152+
* @param[out] out
153+
* Pointer to output byte buffer for address.
154+
* @param[in] outlen
155+
* Length of output byte buffer.
156+
* @return 0 on success
157+
*
158+
*/
159+
int address_from_script_hash(const uint8_t *script_hash,
160+
size_t script_hash_len,
161+
uint8_t *out,
162+
size_t outlen);
163+
164+
```
165+
166+
### Data script outputs
167+
168+
The current UI step for output confirmation has the steps:
169+
- Output number
170+
- Show Address
171+
- Show HTR amount
172+
173+
For an user to confirm a data output we need to replace the address step with a step to confirm the data on the output (ASCII encoded).
174+
175+
We do not require an amount step since the amount of a data output is always 0.01 HTR. The protocol allows for other custom tokens but the Ledger app will only allow HTR.
176+
177+
```c
178+
// Address output confirmation
179+
UX_FLOW(ux_display_tx_output_flow,
180+
&ux_display_review_output_step, // Output <curr>/<total>
181+
&ux_display_address_step, // address
182+
&ux_display_amount_step, // HTR <value>
183+
&ux_display_approve_step, // accept
184+
&ux_display_reject_step, // reject
185+
FLOW_LOOP);
186+
187+
// Data output confirmation
188+
UX_FLOW(ux_display_tx_data_output_flow,
189+
&ux_display_review_output_step, // Output <curr>/<total>
190+
&ux_display_data_script_step, // data from data script
191+
&ux_display_approve_step, // accept
192+
&ux_display_reject_step, // reject
193+
FLOW_LOOP);
194+
```

0 commit comments

Comments
 (0)