Skip to main content

miden_core_lib/handlers/
eddsa_ed25519.rs

1//! EdDSA (Ed25519) signature verification precompile for the Miden VM.
2//!
3//! This precompile mirrors the flow of the existing ECDSA integration but targets Ed25519.
4//! Execution emits an event with pointers to packed bytes (public key, pre-computed challenge
5//! digest `k_digest`, and signature); the host verifies the signature with `miden-crypto`
6//! primitives, returns the result via the advice stack, and logs the calldata for deferred
7//! verification. During proof verification, the stored calldata is re-verified and committed
8//! with the same hashing scheme used at execution time.
9
10use alloc::{vec, vec::Vec};
11use core::convert::TryInto;
12
13use miden_core::{
14    Felt,
15    events::EventName,
16    field::PrimeCharacteristicRing,
17    precompile::{PrecompileCommitment, PrecompileError, PrecompileRequest, PrecompileVerifier},
18    serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
19    utils::bytes_to_packed_u32_elements,
20};
21use miden_crypto::{
22    ZERO,
23    dsa::eddsa_25519_sha512::{PublicKey, Signature},
24    hash::poseidon2::Poseidon2,
25};
26use miden_processor::{
27    ProcessorState,
28    advice::AdviceMutation,
29    event::{EventError, EventHandler},
30};
31
32use crate::handlers::{MemoryReadError, read_memory_packed_u32};
33
34// CONSTANTS
35// ================================================================================================
36
37/// Qualified event name for the EdDSA signature verification event.
38pub const EDDSA25519_VERIFY_EVENT_NAME: EventName =
39    EventName::new("miden::core::dsa::eddsa_ed25519::verify");
40
41const PUBLIC_KEY_LEN_BYTES: usize = 32;
42const K_DIGEST_LEN_BYTES: usize = 64;
43const SIGNATURE_LEN_BYTES: usize = 64;
44
45const PRECOMPILE_REQUEST_LEN: usize =
46    PUBLIC_KEY_LEN_BYTES + K_DIGEST_LEN_BYTES + SIGNATURE_LEN_BYTES;
47
48/// EdDSA (Ed25519) signature verification precompile handler.
49pub struct EddsaPrecompile;
50
51impl EventHandler for EddsaPrecompile {
52    fn on_event(&self, process: &ProcessorState) -> Result<Vec<AdviceMutation>, EventError> {
53        // Stack: [event_id, pk_ptr, k_digest_ptr, sig_ptr, ...]
54        let pk_ptr = process.get_stack_item(1).as_canonical_u64();
55        let k_digest_ptr = process.get_stack_item(2).as_canonical_u64();
56        let sig_ptr = process.get_stack_item(3).as_canonical_u64();
57
58        let pk = {
59            let data_type = DataType::PublicKey;
60            let bytes = read_memory_packed_u32(process, pk_ptr, PUBLIC_KEY_LEN_BYTES)
61                .map_err(|source| EddsaError::ReadError { data_type, source })?;
62            PublicKey::read_from_bytes(&bytes)
63                .map_err(|source| EddsaError::DeserializeError { data_type, source })?
64        };
65
66        let k_digest = {
67            let data_type = DataType::KDigest;
68            let bytes = read_memory_packed_u32(process, k_digest_ptr, K_DIGEST_LEN_BYTES)
69                .map_err(|source| EddsaError::ReadError { data_type, source })?;
70            bytes.try_into().expect("k-digest length must be exactly 64 bytes")
71        };
72
73        let signature = {
74            let data_type = DataType::Signature;
75            let bytes = read_memory_packed_u32(process, sig_ptr, SIGNATURE_LEN_BYTES)
76                .map_err(|source| EddsaError::ReadError { data_type, source })?;
77            Signature::read_from_bytes(&bytes)
78                .map_err(|source| EddsaError::DeserializeError { data_type, source })?
79        };
80
81        let request = EddsaRequest::new(pk, k_digest, signature);
82        let result = request.result();
83
84        Ok(vec![
85            AdviceMutation::extend_stack([Felt::from_bool(result)]),
86            AdviceMutation::extend_precompile_requests([request.into()]),
87        ])
88    }
89}
90
91impl PrecompileVerifier for EddsaPrecompile {
92    fn verify(&self, calldata: &[u8]) -> Result<PrecompileCommitment, PrecompileError> {
93        let request = EddsaRequest::read_from_bytes(calldata)?;
94        Ok(request.as_precompile_commitment())
95    }
96}
97
98// REQUEST
99// ================================================================================================
100
101/// EdDSA verification request containing all data needed to re-run signature verification.
102pub struct EddsaRequest {
103    pk: PublicKey,
104    /// Pre-computed challenge hash k = SHA-512(R || A || message), 64 bytes.
105    k_digest: [u8; K_DIGEST_LEN_BYTES],
106    sig: Signature,
107}
108
109impl EddsaRequest {
110    pub fn new(pk: PublicKey, k_digest: [u8; K_DIGEST_LEN_BYTES], sig: Signature) -> Self {
111        Self { pk, k_digest, sig }
112    }
113
114    pub fn pk(&self) -> &PublicKey {
115        &self.pk
116    }
117
118    pub fn k_digest(&self) -> &[u8; K_DIGEST_LEN_BYTES] {
119        &self.k_digest
120    }
121
122    pub fn sig(&self) -> &Signature {
123        &self.sig
124    }
125
126    pub fn as_precompile_request(&self) -> PrecompileRequest {
127        let mut calldata = Vec::with_capacity(PRECOMPILE_REQUEST_LEN);
128        self.write_into(&mut calldata);
129        PrecompileRequest::new(EDDSA25519_VERIFY_EVENT_NAME.to_event_id(), calldata)
130    }
131
132    pub fn result(&self) -> bool {
133        self.pk.verify_with_unchecked_k(self.k_digest, &self.sig).is_ok()
134    }
135
136    pub fn as_precompile_commitment(&self) -> PrecompileCommitment {
137        let result = Felt::from_bool(self.result());
138        let tag = [EDDSA25519_VERIFY_EVENT_NAME.to_event_id().as_felt(), result, ZERO, ZERO].into();
139
140        let pk_comm = {
141            let felts = bytes_to_packed_u32_elements(&self.pk.to_bytes());
142            Poseidon2::hash_elements(&felts)
143        };
144        let k_digest_comm = {
145            let felts = bytes_to_packed_u32_elements(&self.k_digest);
146            Poseidon2::hash_elements(&felts)
147        };
148        let sig_comm = {
149            let felts = bytes_to_packed_u32_elements(&self.sig.to_bytes());
150            Poseidon2::hash_elements(&felts)
151        };
152
153        let commitment = Poseidon2::merge(&[Poseidon2::merge(&[pk_comm, k_digest_comm]), sig_comm]);
154
155        PrecompileCommitment::new(tag, commitment)
156    }
157}
158
159impl Serializable for EddsaRequest {
160    fn write_into<W: ByteWriter>(&self, target: &mut W) {
161        self.pk.write_into(target);
162        target.write_bytes(&self.k_digest);
163        self.sig.write_into(target);
164    }
165}
166
167impl Deserializable for EddsaRequest {
168    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
169        let pk = PublicKey::read_from(source)?;
170        let k_digest = source.read_array()?;
171        let sig = Signature::read_from(source)?;
172        Ok(Self { pk, k_digest, sig })
173    }
174}
175
176impl From<EddsaRequest> for PrecompileRequest {
177    fn from(request: EddsaRequest) -> Self {
178        request.as_precompile_request()
179    }
180}
181
182// ERRORS
183// ================================================================================================
184
185#[derive(Debug, Clone, Copy)]
186pub(crate) enum DataType {
187    PublicKey,
188    KDigest,
189    Signature,
190}
191
192impl core::fmt::Display for DataType {
193    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
194        match self {
195            DataType::PublicKey => write!(f, "public key"),
196            DataType::KDigest => write!(f, "k-digest"),
197            DataType::Signature => write!(f, "signature"),
198        }
199    }
200}
201
202#[derive(Debug, thiserror::Error)]
203pub(crate) enum EddsaError {
204    #[error("failed to read {data_type} from memory")]
205    ReadError {
206        data_type: DataType,
207        #[source]
208        source: MemoryReadError,
209    },
210
211    #[error("failed to deserialize {data_type}")]
212    DeserializeError {
213        data_type: DataType,
214        #[source]
215        source: DeserializationError,
216    },
217}