-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add secp256r1
host function for signature verification
#1376
Conversation
feature gating
CI cackle is pointing out that |
Resolved. The unsafe |
soroban-env-common/env.json
Outdated
} | ||
], | ||
"return": "Void", | ||
"docs": "Verifies the `signature` using an ECDSA secp256r1 `public_key` on a 32-byte `msg_digest` (produced by applying a secure cryptographic hash function on the message). The `public_key` is expected to be 65 bytes in length, representing a SEC-1 encoded point in uncompressed format. The `signature` is the ECDSA signature `(r, s)` serialized as fixed-size big endian scalar values, both `r`, `s` must be non-zero and `s` must be in the lower range. ", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description of msg_digest
makes it sound like it's expected to be 32-bytes, when it can actually be any length right? The resulting hash of msg_digest
is used in the host function, which is 32 bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same comment applies to the description for recover_key_ecdsa_secp256k1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the msg_digest
is actually the hash of the message pre-applied, so it is 32-bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok I misread hash_from_bytesobj_input
. So those 32 bytes be anything provided by the user? The security warning here says that could be an issue - https://docs.rs/signature/2.1.0/signature/hazmat/trait.PrehashVerifier.html#-security-warning. Did you happen to look into this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I'm guessing it's the users responsibility to make sure msg_digest is the result of a secure cryptographic hash function? Maybe we should mention this in the description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, thanks all for your inputs! It sounds like the best way forward is, for now have two interfaces at the SDK:
- A secure one which accepts the full
message: BytesN
with a hash choice. - A non-secure one marked "hazmat" (with a dangerous looking warning) that accepts
msg_digest: BytesN<32>
We could later (independent of this work) adapt the custom account interface to accept in its signature payload, a new CustomAccountPayload
type (transparent wrapper of BytesN<32>
), and support this type in the secure interface, so we can possibly deprecate the "hazmat" one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. The CustomAccountPayload will need to be a Val compatible type to sit on the function interface boundary, so it'll either need to be a first-class Val type, or one we fake to a degree in the SDK by providing our own type and implementations of the necessary traits. If we do that latter, it won't prevent someone from accepting a BytesN<32>
instead of the specialised type.
We could benefit from that previous data design that @graydon had proposed where Bytes could be annotated with additional information that was persisted. The annotation in this case would indicate that the bytes was produced by a sha256 op. The annotations would be a way for us to, at the env level, provide a real guarantee that any bytes provided had been generated by a local sha256 op. It would allow developers to do things like hash a payload and store the hash, then in a separate invocation accept a signature and verify it signs for that stored hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so it'll either need to be a first-class Val type, or one we fake to a degree in the SDK by providing our own type and implementations of the necessary traits.
I'm not sure it's worth updating the protocol in order to introduce a new, narrow-use type. So I would rather go with just a new SDK type that behaves like BytesN<32>
, but can only be used in this specific place. Does it prevent all the possible footguns? Not really. But does it provide safe usage patterns for the regular SDK users? I think it does.
Something involving more protocol changes like annotations, or a separate type could be considered for the future protocols. OTOH the current host function will stay in the interface anyway, so if we're too concerned about the vulnerability, then we should remove it from protocol 21, or spend more time on the supporting protocol changes.
From my understanding it seems like exploit is unlikely for a contract that doesn't already use this function incorrectly. So I believe that SDK-only harness provides sufficient balance between the necessary implementation effort and encouraging the proper function usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two separate independent discussions here 1. which interfaces we make available to the user in the SDK and 2. what's the best practices for a custom account contract to adapt to such interfaces. We should avoid mixing them together.
The host provides (in this PR) the raw cryptographic primitives for secp256r1 verification, that is well-defined and agreed-upon (in this PR discussion, in the CAP, and the CAP discussion), we are not going to change it (definitely not for P21, hopefully not for a long time).
The SDK bundles the primitives (secp256r1 verification and compute hash) and presents the users with two interfaces: standard and hazmat (described above #1376 (comment)). I have a rough sketch of it in the SDK.
The custom account contract can pick either of the two interfaces. If it picks the standard one, then the signature_payload
will be hashed again, basically disregarding the fact that it is a hash and instead treat it as an opaque payload (which is already what its name suggests). Or it uses the hazmat one, treating (taking the promise from the host that) the signature_payload
has already been hashed, this approach is also not wrong because the hashing is already baked into the host as part of the protocol, and __check_auth
can only be called from the host, not by any random contract.
Either of the two approaches is okay from correctness and safety perspectives. And the discussion around designing a BytesN<32>
wrapper is purely an add-on, extra hint for the user, making it even harder to use the interface wrong. But it is not required for the correctness of the interface, nor should it require significant changes to the existing env typing or protocol to accommodate it. We should discuss the wrapper type and custom account adaptation in a separate issue (or in the SDK PR linked above) to prevent this thread from ever-growing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continuing discussion about the SDK interface over on the SDK PR here:
I'm a bit nervous about the way you're tunneling through the protocol gate to access the host function before it's available, though I understand why and suspect it's the best option for now. In the change I'm getting started on now (protocol-qualification of all the tests and un-gating the next stuff) we can remove this sort of thing. |
Just making sure I understand your concern of "tunneling through the protocol gate". I believe you are talking about the tests that are directly calling the new method via |
Yes, exactly. If you notice in the observations recorded for the test in this PR, they don't contain the calls to the new host function at all, because you're actually bypassing the point where tracing machinery would normally hook in to observe the call. I think it will probably be something we can roll back when we have protocol-qualified testing, and is ok for now. Just something I need to remember to come back to. |
remove println
### What Add support for secp256r1 signature verification (picking up env changes stellar/rs-soroban-env#1376). And adapts the existing `Crypto` module by split the cryptographic functions into two sets: - `Crypto`: standard, recommended set of cryptographic functions. This includes `secp256k1_recover` and `secp256r1_verify` taking the full `message: Bytes` as input and performs hashing underneath. - `CryptoHazmat`: hazardous material. Contains low-level, unsecure if used incorrectly functions including `secp256k1_recover_prehash` and `secp256r1_verify_prehash`, taking a `message_hash: BytesN<32>` as input. Design rationales were discussed in the env PR (started on stellar/rs-soroban-env#1376 (comment) and onward). XDR changes associated with the new contract spec: stellar/stellar-xdr#182, stellar/stellar-xdr#183 rs-xdr: stellar/rs-stellar-xdr#361 End-to-end with secp256r1 account contract: stellar/rs-soroban-env#1402 ### Why [TODO: Why this change is being made. Include any context required to understand the why.] ### Known limitations [TODO or N/A] --------- Co-authored-by: Leigh McCulloch <[email protected]>
What
Resolves #807 by adding a new host function
verify_sig_ecdsa_secp256r1
for ECDSA signature verification using secp256r1 curve. The function accepts following inputs:public_key: BytesObject
containing the 65-byte SEC-1 uncompressed ECDSA public keymsg_digest: BytesObject
a 32-byte hash of the messagesignature
: the 64-byte signature(r, s)
serialized as fixed-width big endian scalarsThe function is gated behind protocol 21 (
min_supported_protocol = 21
).Several key interface decisions have been extensively discussed in stellar/stellar-protocol#1435
PR with the associated XDR changes: stellar/stellar-xdr#178, stellar/rs-stellar-xdr#355
SDK PR adapting to this change: stellar/rs-soroban-sdk#1246
Metering and Calibration
Two new cost types have been newly added:
Sec1DecodePointUncompressed
: constant cost type representing the cost to decode thepublic_key
VerifyEcdsaSecp256r1Sig
: constant cost type represent the cost of ECDSA sig verificationA prevous cost type
ComputeEcdsaSecp256k1Sig
has been renamed toDecodeEcdsaCurve256Sig
, which represents the cost of deserializing both thesecp256k1
andsecp256r1
signatures.Calibration:
Calibration results has been posted and discussed in stellar/stellar-protocol#1435 (comment)
Testing
Unit tests have been added to test against various forms of invalid inputs.
In addition, two set of test vectors has been added in integration test: