Skip to content

Commit 4b1eaaf

Browse files
0xRigeldeanmlittleptaffet-jump
authored
SIMD-0075: Secp256r1 Precompile (Supersedes SIMD-0048) (#75)
* Update and rename 0048-native-program-for-secp256r1-sigverify.md to 0048-precompile-for-secp256r1-sigverify.md Add specification & detail Add new security considerations * linting * linting * spelling * spelling * Fixed wording and technical documentation * linting * add: author * fix: review changes * fix: typos * refactor: implementation logic argumentation * remove: SIMD-0048 * linting * linting * fix: typo * fix: typo * add: supersedes section to header * Revert "remove: SIMD-0048" This reverts commit b8dcd34. * change: old SIMD status to withdrawn * remove: Deprecated note for linting * fix: SIMD should be timeless * Revert: simd 0048 rename & add superseded-by to header * edit: supersedes field on simd 0075 * rm: superfluous/unnecessary note from design section * general improvements - revise language as per rfc2119 - move rfc design details to security considerations - add language agnostic pseudocode structs * fixes * fix: linting * reset: changes to simd-0048 * Update proposals/0075-precompile-for-secp256r1-sigverify.md Co-authored-by: Philip Taffet <123486962+ptaffet-jump@users.noreply.github.com> * add: length prefix & array padding comment * fix: missing byte of padding in line 196 * add: offset & indices checks for signature, message and publickey * fix: linting * fix: indentation * refactor: modify pseudocode and data structs to comply with SIMD 0152 * formatting * linting * formatting * resolve comments --------- Co-authored-by: aresastro <dean@bitping.com> Co-authored-by: Philip Taffet <123486962+ptaffet-jump@users.noreply.github.com>
1 parent 53da177 commit 4b1eaaf

File tree

1 file changed

+381
-0
lines changed

1 file changed

+381
-0
lines changed
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
---
2+
simd: "0075"
3+
title: Precompile for verifying secp256r1 sig.
4+
authors:
5+
- Orion (Bunkr)
6+
- Jstnw (Bunkr)
7+
- Dean (Web3 Builders Alliance)
8+
category: Standard
9+
type: Core
10+
status: Draft
11+
created: 2024-02-27
12+
feature: (fill in with feature tracking issues once accepted)
13+
supersedes: "0048"
14+
---
15+
16+
## Summary
17+
18+
Adding a precompile to support the verification of signatures generated on
19+
the secp256r1 curve. Analogous to the support for secp256k1 and ed25519
20+
signatures that already exists in form of the
21+
`KeccakSecp256k11111111111111111111111111111` and
22+
`Ed25519SigVerify111111111111111111111111111` precompiles.
23+
24+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
25+
NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and
26+
"OPTIONAL" in this document are to be interpreted as described in
27+
RFC 2119.
28+
29+
## Motivation
30+
31+
Solana has the opportunity to leverage the secure element of users' existing
32+
mobile devices to support more user-friendly self-custodial security solutions.
33+
The status quo of air-gapping signing with a hardware wallet currently requires
34+
specialty hardware and still represents a single point of failure. Multi-signature
35+
wallets provide enhanced security through multi-party signing, however the UX
36+
is cumbersome due to the need to sign transactions multiple times and manage
37+
multiple seed phrases. A much more ergonomic approach combining the best of
38+
these two solutions on generalised mobile hardware could be achieved by adding
39+
support for secp256r1 signatures.
40+
41+
There are already several standardised implementations of this, such as Passkeys
42+
and WebAuthn. These solutions leverage Apple's Secure Enclave and Android Keystore
43+
to enable users to save keypairs associated to different services natively on
44+
the secure element of their mobile devices. To authenticate with
45+
those services, the user uses their biometrics to sign a message with the stored
46+
private key.
47+
48+
While originally intended to solve for password-less authentication in Web2
49+
applications, WebAuthn and Passkeys also make an excellent candidate for on-chain
50+
second-factor authentication. Beyond simply securing funds, there are also many
51+
other potential beneficial abstractions that could make use of the simple UX
52+
they provide.
53+
54+
Although WebAuthn supports the following curves:
55+
56+
- P-256
57+
- P-384
58+
- P-521
59+
- ed25519
60+
61+
P-256 is the only one supported by both Android & MacOS/iOS (MacOS/iOS being the
62+
more restrictive of the two), hence the goal being to implement secp256r1 signature
63+
verification
64+
65+
General Documentation:
66+
67+
[WebAuthn](https://webauthn.io/)
68+
69+
[Passkeys](https://fidoalliance.org/passkeys/)
70+
71+
**Note: P-256 / secp256r1 / prime256v1 are used interchangably in this document
72+
as they represent the same elliptic curve. The choice of nomenclature depends on
73+
what RFC or SEC document is being referenced.**
74+
75+
## Alternatives Considered
76+
77+
We have discussed the following alternatives:
78+
79+
1.) Realising signature verification with a syscall similar
80+
to `secp256k1_recover()` instead of a precompile. This would ease
81+
integration for developers, since no instruction introspection would be
82+
required when utilizing the syscall. This is still a valid consideration.
83+
84+
2.) Realising signature verification through and on-chain sBPF implemenation. On
85+
a local validator a single signature verification consumes ≈42M compute units.
86+
A possibility would be to split the verification into multiple transactions.
87+
This would most probably require off-chain infrastructure to crank the process
88+
or carry higher transaction fees for the end user. (similar to the current elusiv
89+
protocol private transfer)
90+
We feel this alternative directly contradicts and impinges on the main upside of
91+
passkeys, which is the incredible UX and ease of use to the end user.
92+
93+
3.) Allowing for high-S signatures was considered, however the pitfalls
94+
of signature malleability are too great to leave open to implementation.
95+
96+
4.) Allowing for uncompressed keys was considered, however as we are already
97+
taking an opinionated stance on signature malleability, it makes sense to
98+
also take an opinionated stance on public key encoding.
99+
100+
## New Terminology
101+
102+
None
103+
104+
## Detailed Design
105+
106+
The precompile's purpose is to verify signatures using ECDSA-256.
107+
(denoted in [RFC6460](https://www.ietf.org/rfc/rfc6460.txt) as
108+
ECDSA using the NIST P-256 curve and the SHA-256 hashing algorithm)
109+
110+
Apart from the RFC mandated implementation the precompile must additionally take
111+
an opinionated stance on signature malleability.
112+
113+
### Signature Malleability
114+
115+
Due to X axis symmetry along the elliptic curve, for any ECDSA signature
116+
`(r, s)`, there also exists a valid signature `(r, n - s)`, where `n` is the
117+
order of the curve. This introduces "s malleability", allowing an attacker
118+
to produce an alternative version of `s` without invalidating the signature.
119+
120+
The pitfalls of this in authentication systems can be particularly perilous,
121+
opening up certain implementations to signature replay attacks over the same
122+
message by simply flipping the `s` value over the curve.
123+
124+
As the primary goal of the `secp256r1` program is secure signature validation
125+
for authentication purposes, the precompile must mitigate these attacks
126+
by enforcing the usage of `lowS` values, in which `s <= n/2`.
127+
128+
As such, the program must immediately fail upon the detection of any
129+
signature that includes a `highS` value. This prevents any accidental
130+
succeptibility to signature malleability attacks.
131+
132+
Note: The existing `secp256k1` precompile makes no attempt attempt to mitigate
133+
s malleability, as doing so would go against its primary goal of achieving
134+
`ecrecover` parity with EVM.
135+
136+
### Implementation
137+
138+
### Program
139+
140+
ID: `Secp256r1SigVerify1111111111111111111111111`
141+
142+
In accordance with [SIMD
143+
0152](https://github.com/solana-foundation/solana-improvement-documents/pull/152)
144+
the programs ```verify``` instruction must accept the following data:
145+
146+
In Pseudocode:
147+
148+
```
149+
struct Secp256r1SigVerifyInstruction {
150+
num_signatures: uint8 LE, // Number of signatures to verify
151+
padding: uint8 LE, // Single byte padding
152+
offsets: Array<Secp256r1SignatureOffsets>, // Array of offset structs
153+
additionalData?: Bytes, // Optional additional data, e.g.
154+
// signatures included in the same
155+
// instruction
156+
}
157+
Note: Array<Secp256r1SignatureOffsets> does not contain any length prefixes or
158+
padding between elements.
159+
160+
struct Secp256r1SignatureOffsets {
161+
signature_offset: uint16 LE, // Offset to signature
162+
signature_instruction_index: uint16 LE, // Instruction index to signature
163+
public_key_offset: uint16 LE, // Offset to public key
164+
public_key_instruction_index: uint16 LE, // Instruction index to public key
165+
message_offset: uint16 LE, // Offset to start of message data
166+
message_length: uint16 LE, // Size of message data
167+
message_instruction_index: uint16 LE, // Instruction index to message
168+
}
169+
```
170+
171+
Up to 8 signatures can be verified. If any of the signatures fail to verify,
172+
an error must be returned.
173+
174+
In accordance with [SIMD
175+
0152](https://github.com/solana-foundation/solana-improvement-documents/pull/152)
176+
the behavior of the program must be as follows:
177+
178+
1. If instruction `data` is empty, return error.
179+
2. The first byte of `data` is the number of signatures `num_signatures`.
180+
3. If `num_signatures` is 0, return error.
181+
4. Expect (enough bytes of `data` for) `num_signatures` instances of
182+
`Secp256r1SignatureOffsets`.
183+
5. For each signature:
184+
a. Read `offsets`: an instance of `Secp256r1SignatureOffsets`
185+
b. Based on the `offsets`, retrieve `signature`, `public_key`, and
186+
`message` bytes. If any of the three fails, return error.
187+
c. Invoke the actual `sigverify` function. If it fails, return error.
188+
189+
To retrieve `signature`, `public_key`, and `message`:
190+
191+
1. Get the `instruction_index`-th `instruction_data`
192+
- The special value `0xFFFF` means "current instruction"
193+
- If the index is invalid, return Error
194+
2. Return `length` bytes starting from `offset`
195+
- If this exceeds the `instruction_data` length, return Error
196+
197+
Note that fields (offsets) can overlap, for example the same public key or
198+
message can be referred to by multiple instances of `Secp256r1SignatureOffsets`.
199+
200+
If the precompile `verify` function returns any error, the whole transaction
201+
should fail. Therefore, the type of error is irrelevant and is left as an
202+
implementation detail.
203+
204+
The instruction processing logic must follow the pseudocode below:
205+
206+
```
207+
/// `data` is the secp256r1 program's instruction data. `instruction_datas` is
208+
/// the full slice of instruction datas for all instructions in the transaction,
209+
/// including the secp256r1 program's instruction data.
210+
211+
/// length_of_data is the length of `data`
212+
213+
/// SERIALIZED_OFFSET_STRUCT_SIZE is the length of the serialized
214+
/// Secp256r1SignatureOffsets struct
215+
216+
/// SERIALIZED_PUBLIC_KEY_LENGTH and SERIALIZED_SIGNATURE_LENGTH represent the
217+
/// length of the serialized public key and signature respectively
218+
219+
function verify() {
220+
if length_of_data == 0 {
221+
return Error
222+
}
223+
num_signatures = data[0]
224+
if num_signatures == 0 && length_of_data > 1 {
225+
return Error
226+
}
227+
if length_of_data < (num_signatures * SERIALIZED_OFFSET_STRUCT_SIZE + 2) {
228+
return Error
229+
}
230+
all_tx_data = { data, instruction_datas }
231+
data_start_position = 2
232+
233+
for i in 0..num_signatures {
234+
offsets = (Secp256r1SignatureOffsets)
235+
all_tx_data.data[data_start_position..data_start_position + SERIALIZED_OFFSET_STRUCT_SIZE]
236+
data_position += SERIALIZED_OFFSET_STRUCT_SIZE
237+
238+
signature = get_data_slice(all_tx_data,
239+
offsets.signature_instruction_index,
240+
offsets.signature_offset
241+
signature_length)
242+
if !signature {
243+
return Error
244+
}
245+
246+
public_key = get_data_slice(all_tx_data,
247+
offsets.public_key_instruction_index,
248+
offsets.public_key_offset,
249+
SERIALIZED_PUBLIC_KEY_LENGTH)
250+
if !public_key {
251+
return Error
252+
}
253+
254+
message = get_data_slice(all_tx_data,
255+
offsets.message_instruction_index,
256+
offsets.message_offset
257+
offsets.message_length)
258+
if !message {
259+
return Error
260+
}
261+
262+
// sigverify includes validating signature and public_key
263+
// the additional highS check is done here
264+
if signature_S == highS {
265+
return Error
266+
}
267+
result = sigverify(signature, public_key, message)
268+
if result != Success {
269+
return Error
270+
}
271+
}
272+
return Success
273+
}
274+
// This function is re-used across precompiles in accordance with SIMD-0152
275+
fn get_data_slice(all_tx_data, instruction_index, offset, length) {
276+
// Get the right instruction_data
277+
if instruction_index == 0xFFFF {
278+
instruction_data = all_tx_data.data
279+
} else {
280+
if instruction_index >= num_instructions {
281+
return Error
282+
}
283+
instruction_data = all_tx_data.instruction_datas[instruction_index]
284+
}
285+
286+
start = offset
287+
end = offset + length
288+
if end > instruction_data_length {
289+
return Error
290+
}
291+
292+
return instruction_data[start..end]
293+
}
294+
```
295+
296+
Additonally the precompile's core `verify` function must be constructed in
297+
accordance with the structure outlined in [sdk/src/precompiles.rs](https://github.com/solana-labs/solana/blob/9ffbe2afd8ab5b972c4ad87d758866a3e1bb87fb/sdk/src/precompiles.rs).
298+
299+
### Compute Cost / Efficiency
300+
301+
Benchmarking and compute cost calculations must be done in accordance with [SIMD-0121](https://github.com/solana-foundation/solana-improvement-documents/pull/121)
302+
303+
Additionally, comparisons to existing precompiles should be done to check for
304+
comperable efficiency.
305+
306+
## Impact
307+
308+
Would enable the on-chain usage of Passkeys and the WebAuthn Standard, and
309+
turn the vast majority of modern smartphones into native hardware wallets.
310+
311+
By extension, this would also enable the creation of account abstractions and
312+
forms of Two-Factor Authentication around those keypairs.
313+
314+
## Security Considerations
315+
316+
The following security considerations must be made for the
317+
implementation of ECDSA over NIST P-256.
318+
319+
### Curve
320+
321+
The curve parameters for NIST P-256/secp256r1/prime256v1 are
322+
outlined in the [SEC2](https://www.secg.org/SEC2-Ver-1.0.pdf#page=21)
323+
document in Section 2.7.2
324+
325+
### Point Encoding/Decoding
326+
327+
The precompile must accept SEC1 encoded points in compressed form.
328+
The encoding and decoding of these is outlined in sections
329+
`2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion`
330+
and `2.3.4 Octet-String-to-Elliptic-Curve-Point Conversion`
331+
found in [SEC1](https://www.secg.org/sec1-v2.pdf#page=16).
332+
333+
The SEC1 encoded EC point P = (x_p, y_p) in compressed form consists
334+
of 33 bytes (octets). The first byte of 02_16 / 03_16 signifies a
335+
compressed point, as well as whether y_p is odd or even. The remaining
336+
32 bytes represent x_p converted into a 32 octet string.
337+
338+
While SEC1 encoded uncompressed points could also be used,
339+
due to their larger size of 65 bytes, the ease of transformation
340+
between uncompressed and compressed points, and the vast majority
341+
of applications exclusively making use of compressed points, it
342+
seems a reasonable consideration to save 32 bytes of instruction
343+
data with a protocol that only accepts compressed points.
344+
345+
### ECDSA / Signature Verification
346+
347+
The precompile must implement the `Verifying Operation` outlined in
348+
[SEC1](https://www.secg.org/sec1-v2.pdf#page=52)
349+
in Section 4.1.4 as well as in the
350+
[Digital Signature Standard (DSS)](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf#page=36)
351+
document in Section 6.4.2.
352+
353+
A multitude of test vectors to verify correctness can
354+
be found in
355+
[RFC6979](https://datatracker.ietf.org/doc/html/rfc6979#appendix-A.2.5)
356+
in Section A.2.5 as well as at the
357+
[NIST CAVP](https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/digital-signatures#ecdsa2vs)
358+
(Cryptographic Algorithm Validation Program)
359+
360+
### General
361+
362+
As multiple other clients are being developed, it is imperative that there is
363+
bit-level reproducibility between the precompile implementations, especially
364+
with regard to cryptographic operations. Any discrepancy between implementations
365+
could cause a fork and or a chain halt.
366+
367+
As such we would propose the following:
368+
369+
- Development of a thorough test suite that includes all test vectors as well
370+
as tests from the
371+
[Wycheproof Project](https://github.com/google/wycheproof#project-wycheproof)
372+
373+
- Maintaining active communication with other clients to ensure parity and to
374+
support potential changes if they arise.
375+
376+
## Backwards Compatibility
377+
378+
Transactions using the instruction could not be used on Solana versions which don't
379+
implement this feature. A Feature gate should be used to enable this feature
380+
when the majority of the cluster is using the required version. Transactions
381+
that do not use this feature are not impacted.

0 commit comments

Comments
 (0)