Skip to content

Conversation

@raychu86
Copy link
Collaborator

@raychu86 raychu86 commented Sep 3, 2025

Motivation

This PR introduces native ECDSA signature verification to on-chain (finalize) execution to improve interoperability and broaden supported signature formats. With on-chain verification, programs can trust externally signed messages and approvals without off-chain helpers, enabling cross-chain messaging, asset flows, multisig/governance, etc. The initial scope focuses on verification with a simple canonical representation and deterministic, consensus-safe behavior, while leaving room to add other schemes and encodings over time.

The list of new ecdsa.verify.<variant> instructions are as follows:

  • ecdsa.verify.keccak256
  • ecdsa.verify.keccak256.raw
  • ecdsa.verify.keccak256.eth
  • ecdsa.verify.keccak384
  • ecdsa.verify.keccak384.raw
  • ecdsa.verify.keccak384.eth
  • ecdsa.verify.keccak512
  • ecdsa.verify.keccak512.raw
  • ecdsa.verify.keccak512.eth
  • ecdsa.verify.sha3_256
  • ecdsa.verify.sha3_256.raw
  • ecdsa.verify.sha3_256.eth
  • ecdsa.verify.sha3_384
  • ecdsa.verify.sha3_384.raw
  • ecdsa.verify.sha3_384.eth
  • ecdsa.verify.sha3_512
  • ecdsa.verify.sha3_512.raw
  • ecdsa.verify.sha3_512.eth

The variant types allow the users to select the underlying hash function used, the serialization type (with .raw), and if the address being verified is a 20-byte Ethereum address. The .raw variants indicate if the message should be serialized in it's raw bit form or with Aleo specific variant bits; this is necessary to ensure compatibility with external signing libraries. By default the .eth variants will serialize the message in it's raw form, so there is no need for an additional .eth.raw suffixed variant.

A few additional features are also included:

  • .raw serialization variants

    • All existing hash opcodes will now have a new variant by appending .raw to the end.
    • sign.verify.raw is also introduced to
    • This performs the hash on the raw serialization of the pre-image that does not contain any Aleo specific variant bits
  • Increasing array size from 32 to 256.

Test Plan

Local Devnet

TODO

  • Add additional integration tests
  • Add input type checks for the ECDSA opcode operands
  • Add array size increase checks for pre-V11 programs
  • Perform stress testing for the array size increase

Questions for Auditors

  • Is the removal of the terminator bit in the ToFieldsRaw implementation safe?

@raychu86 raychu86 changed the title [WIP] [Feat] Introduce ECDSA verification opcodes [Feat] Introduce ECDSA verification opcodes Sep 5, 2025
Signed-off-by: Raymond Chu <14917648+raychu86@users.noreply.github.com>
@raychu86 raychu86 marked this pull request as ready for review September 5, 2025 23:08
Copy link
Collaborator

@vicsn vicsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks excellent, from my humble perspective. 🎆

Can you update the docstring for ConsensusVersion::V11?

}

/// Unary operator for converting to raw bits.
pub trait ToBitsRaw: ToBits {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inactionable rant: long term it might make more sense to have trait ToBits: ToBitsRaw, as I understand that to_bits serialization could call to_bits_raw internally. But then again, we don't want to enable raw serialization for all types, so I guess the current approach is fine and helpful.

#[inline]
fn write_bits_raw_le(&self, vec: &mut Vec<Self::Boolean>) {
// The slice is order-preserving, meaning the first circuit in is the first circuit bits out.
for elem in self.iter() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the element-wise little endian serialization is bound to lead to confusion so we should really document this well on the SDK sode.

If you agree, do you want to note this explicitly in the PR description so it'll make its way into the release notes?

CC @Roee-87 just FYI

(16, _) => bail!("'hash_many.psd4' is not yet implemented"),
(17, _) => bail!("'hash_many.psd8' is not yet implemented"),
(18.., _) => bail!("Invalid 'hash' variant: {}", $variant),
(18, _) => $q($N::hash_to_group_bhp256(&bits_raw()))?.into(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double checking: are we sure we don't have to guard this by Address | Group like we do for some other match arms? (Meaning the previous code would have a bug)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hash_to_group* for the BHP and Pedersen variants is the uncompressed version. If you look at their corresponding hash.* (without to_group), you'll see that it calls the uncompressed hash and then casts it to a field.

We guard for Poseidon because its standard version outputs a field. hash_to_group_psd encodes with Elligator. I could not tell you what security properties this affords.

}

#[test]
fn test_ecdsa_signature_vector_1() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any canonical set of test vectors which we can use, for ECDSA and/or Ethereum's usage?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still waiting on test vectors from partners, however we should pull in any standard library of ECDSA test vectors if they are available.

31, 32
);
// Implement for `[U8<N>, SIZE]` for sizes 1 through 256.
seq_macro::seq!(S in 1..=256 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of code generation. Which will increase even more if we have a larger array size. The use of seq! feels like an antipattern here, albeit a pretty one.

What about we have just a single impl<N: Network> From<impl Iterator<Item = $element<N>>> for Plaintext<N>? To not have to think about differing guarantees we could ensure we don't surpass the maximum array length in there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also use ExactSizeIterator

@raychu86 raychu86 requested review from Roee-87 and d0cd September 12, 2025 23:12

/// Returns `true` if the program contains an array type with a size that exceeds the given maximum.
pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
self.mappings.values().any(|mapping| mapping.exceeds_max_array_size(max_array_size))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can mapping keys also be arrays? MapKey contains a generic PlaintextType so maybe we should just check.

Copy link
Collaborator

@d0cd d0cd Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


#[cfg(feature = "test")]
#[test]
fn test_ecdsa_migration() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a proponent of these sorts of tests going in a test_v* file. It helps us easily look back on our test coverage for each consensus version.

@d0cd d0cd mentioned this pull request Sep 26, 2025
@vicsn vicsn closed this pull request by merging all changes into staging in b1dde7d Oct 14, 2025
@vicsn vicsn deleted the feat/ecdsa branch October 14, 2025 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants