Skip to main content

mina_p2p_messages/v2/
hashing.rs

1use std::{fmt, io, sync::Arc};
2
3use binprot::{BinProtRead, BinProtWrite};
4use generated::MinaStateBlockchainStateValueStableV2;
5use mina_curves::pasta::Fp;
6use poseidon::hash::{
7    hash_with_kimchi,
8    params::{MINA_PROTO_STATE, MINA_PROTO_STATE_BODY},
9    Inputs,
10};
11use serde::{Deserialize, Serialize};
12use sha2::{
13    digest::{generic_array::GenericArray, typenum::U32},
14    Digest, Sha256,
15};
16
17use crate::{
18    bigint::{BigInt, InvalidBigInt},
19    hash::MinaHash,
20    hash_input::FailableToInputs,
21};
22
23use super::{
24    generated, ConsensusBodyReferenceStableV1, ConsensusGlobalSlotStableV1,
25    ConsensusProofOfStakeDataConsensusStateValueStableV2,
26    ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1,
27    ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1,
28    ConsensusVrfOutputTruncatedStableV1, DataHashLibStateHashStableV1, LedgerHash,
29    MinaBaseControlStableV2, MinaBaseEpochLedgerValueStableV1, MinaBaseFeeExcessStableV1,
30    MinaBaseLedgerHash0StableV1, MinaBasePendingCoinbaseHashBuilderStableV1,
31    MinaBasePendingCoinbaseHashVersionedStableV1, MinaBasePendingCoinbaseStackVersionedStableV1,
32    MinaBasePendingCoinbaseStateStackStableV1, MinaBaseProtocolConstantsCheckedValueStableV1,
33    MinaBaseStagedLedgerHashNonSnarkStableV1, MinaBaseStagedLedgerHashStableV1,
34    MinaBaseStateBodyHashStableV1, MinaBaseZkappCommandTStableV1WireStableV1AccountUpdatesAA,
35    MinaNumbersGlobalSlotSinceGenesisMStableV1, MinaNumbersGlobalSlotSinceHardForkMStableV1,
36    MinaNumbersGlobalSlotSpanStableV1, MinaStateBlockchainStateValueStableV2LedgerProofStatement,
37    MinaStateBlockchainStateValueStableV2LedgerProofStatementSource,
38    MinaStateBlockchainStateValueStableV2SignedAmount, MinaStateProtocolStateBodyValueStableV2,
39    MinaStateProtocolStateValueStableV2,
40    MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1,
41    NonZeroCurvePointUncompressedStableV1, PendingCoinbaseHash, SgnStableV1, SignedAmount,
42    StateHash, TokenFeeExcess,
43};
44
45impl generated::MinaBaseStagedLedgerHashNonSnarkStableV1 {
46    pub fn sha256(&self) -> GenericArray<u8, U32> {
47        let mut ledger_hash_bytes: [u8; 32] = [0; 32];
48
49        ledger_hash_bytes.copy_from_slice(&self.ledger_hash.to_bytes()[..]);
50        ledger_hash_bytes.reverse();
51
52        let mut hasher = Sha256::new();
53        hasher.update(ledger_hash_bytes);
54        hasher.update(self.aux_hash.as_ref());
55        hasher.update(self.pending_coinbase_aux.as_ref());
56
57        hasher.finalize()
58    }
59}
60
61impl generated::ConsensusVrfOutputTruncatedStableV1 {
62    pub fn blake2b(&self) -> Vec<u8> {
63        use blake2::{
64            digest::{Update, VariableOutput},
65            Blake2bVar,
66        };
67        let mut hasher = Blake2bVar::new(32).expect("Invalid Blake2bVar output size");
68        hasher.update(&self.0);
69        hasher.finalize_boxed().to_vec()
70    }
71}
72
73#[derive(Hash, Eq, PartialEq, Ord, PartialOrd, Clone)]
74pub struct TransactionHash(Arc<[u8; 32]>);
75
76impl std::str::FromStr for TransactionHash {
77    type Err = bs58::decode::Error;
78
79    fn from_str(s: &str) -> Result<Self, Self::Err> {
80        let bytes = bs58::decode(s).with_check(Some(0x1D)).into_vec()?;
81        dbg!(bytes.len());
82        let bytes = (&bytes[2..])
83            .try_into()
84            .map_err(|_| bs58::decode::Error::BufferTooSmall)?;
85        Ok(Self(Arc::new(bytes)))
86    }
87}
88
89impl fmt::Display for TransactionHash {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        let mut bytes = [32; 33];
92        bytes[1..].copy_from_slice(&*self.0);
93        bs58::encode(bytes)
94            .with_check_version(0x1D)
95            .into_string()
96            .fmt(f)
97    }
98}
99
100impl fmt::Debug for TransactionHash {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        write!(f, "{:?}", self.0)
103        // write!(f, "TransactionHash({})", self)
104    }
105}
106
107impl From<&[u8; 32]> for TransactionHash {
108    fn from(value: &[u8; 32]) -> Self {
109        Self(Arc::new(*value))
110    }
111}
112
113impl Serialize for TransactionHash {
114    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
115    where
116        S: serde::Serializer,
117    {
118        if serializer.is_human_readable() {
119            serializer.serialize_str(&self.to_string())
120        } else {
121            serde_bytes::serialize(&*self.0, serializer)
122        }
123    }
124}
125
126impl<'de> serde::Deserialize<'de> for TransactionHash {
127    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
128    where
129        D: serde::Deserializer<'de>,
130    {
131        if deserializer.is_human_readable() {
132            let b58: String = Deserialize::deserialize(deserializer)?;
133            Ok(b58.parse().map_err(|err| serde::de::Error::custom(err))?)
134        } else {
135            serde_bytes::deserialize(deserializer)
136                .map(Arc::new)
137                .map(Self)
138        }
139    }
140}
141
142impl BinProtWrite for TransactionHash {
143    fn binprot_write<W: io::Write>(&self, w: &mut W) -> std::io::Result<()> {
144        w.write_all(&*self.0)
145    }
146}
147
148impl BinProtRead for TransactionHash {
149    fn binprot_read<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, binprot::Error>
150    where
151        Self: Sized,
152    {
153        let mut bytes = [0; 32];
154        r.read_exact(&mut bytes)?;
155        Ok(Self(bytes.into()))
156    }
157}
158
159#[cfg(feature = "openapi")]
160const _: () = {
161    use utoipa::{PartialSchema, ToSchema};
162
163    impl PartialSchema for TransactionHash {
164        fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
165            // TODO(openapi): add description, example, and base58 pattern
166            <String as PartialSchema>::schema()
167        }
168    }
169    impl ToSchema for TransactionHash {
170        fn name() -> std::borrow::Cow<'static, str> {
171            std::borrow::Cow::Borrowed("TransactionHash")
172        }
173    }
174};
175
176impl generated::MinaTransactionTransactionStableV2 {
177    pub fn hash(&self) -> io::Result<TransactionHash> {
178        match self {
179            Self::Command(v) => v.hash(),
180            Self::FeeTransfer(_) => Err(io::Error::new(
181                io::ErrorKind::Unsupported,
182                "fee transfer tx hashing is not yet supported",
183            )),
184            Self::Coinbase(_) => Err(io::Error::new(
185                io::ErrorKind::Unsupported,
186                "coinbase tx hashing is not yet supported",
187            )),
188        }
189    }
190}
191
192impl generated::MinaBaseUserCommandStableV2 {
193    pub fn hash(&self) -> io::Result<TransactionHash> {
194        match self {
195            Self::SignedCommand(v) => v.hash(),
196            Self::ZkappCommand(v) => v.hash(),
197        }
198    }
199}
200
201impl generated::MinaBaseSignedCommandStableV2 {
202    pub fn binprot_write_with_default_sig(&self) -> io::Result<Vec<u8>> {
203        let default_signature = generated::MinaBaseSignatureStableV1(BigInt::one(), BigInt::one());
204
205        let mut encoded = vec![];
206        self.payload.binprot_write(&mut encoded)?;
207        self.signer.binprot_write(&mut encoded)?;
208        default_signature.binprot_write(&mut encoded)?;
209        Ok(encoded)
210    }
211
212    pub fn hash(&self) -> io::Result<TransactionHash> {
213        use blake2::{
214            digest::{Update, VariableOutput},
215            Blake2bVar,
216        };
217        let mut hasher = Blake2bVar::new(32).expect("Invalid Blake2bVar output size");
218
219        hasher.update(&self.binprot_write_with_default_sig()?);
220        let mut hash = [0; 32];
221        hasher
222            .finalize_variable(&mut hash)
223            .expect("Invalid buffer size"); // Never occur
224
225        Ok(TransactionHash(hash.into()))
226    }
227}
228
229// TODO(adonagy): reduce duplication
230impl generated::MinaBaseZkappCommandTStableV1WireStableV1 {
231    fn binprot_write_with_default(&self) -> io::Result<Vec<u8>> {
232        let default_signature = generated::MinaBaseSignatureStableV1(BigInt::one(), BigInt::one());
233
234        let mut encoded = vec![];
235
236        let mut modified = self.clone();
237
238        modified.fee_payer.authorization = default_signature.clone().into();
239
240        modified.account_updates.iter_mut().for_each(|u| {
241            Self::replace_auth_recursive(&mut u.elt);
242        });
243
244        modified.binprot_write(&mut encoded)?;
245        Ok(encoded)
246    }
247
248    fn replace_auth_recursive(
249        update_elt: &mut MinaBaseZkappCommandTStableV1WireStableV1AccountUpdatesAA,
250    ) {
251        Self::replace_auth(&mut update_elt.account_update.authorization);
252        // if update_elt.calls.is_empty() {
253        //     return;
254        // }
255
256        update_elt.calls.iter_mut().for_each(|call| {
257            Self::replace_auth_recursive(&mut call.elt);
258        });
259
260        // for call in update_elt.calls.iter_mut() {
261        //     Self::replace_auth_recursive(&mut call.elt);
262        // }
263    }
264
265    pub fn replace_auth(auth: &mut MinaBaseControlStableV2) {
266        let default_signature = generated::MinaBaseSignatureStableV1(BigInt::one(), BigInt::one());
267        let default_proof = super::dummy_transaction_proof();
268        *auth = match auth {
269            MinaBaseControlStableV2::Proof(_) => {
270                MinaBaseControlStableV2::Proof(Box::new(default_proof.0.clone().into()))
271            }
272            MinaBaseControlStableV2::Signature(_) => {
273                MinaBaseControlStableV2::Signature(default_signature.clone().into())
274            }
275            MinaBaseControlStableV2::NoneGiven => MinaBaseControlStableV2::NoneGiven,
276        };
277    }
278
279    pub fn hash(&self) -> io::Result<TransactionHash> {
280        use blake2::{
281            digest::{Update, VariableOutput},
282            Blake2bVar,
283        };
284        let mut hasher = Blake2bVar::new(32).expect("Invalid Blake2bVar output size");
285
286        hasher.update(&self.binprot_write_with_default()?);
287        let mut hash = [0; 32];
288        hasher
289            .finalize_variable(&mut hash)
290            .expect("Invalid buffer size"); // Never occur
291
292        Ok(TransactionHash(hash.into()))
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::{super::manual, *};
299    use manual::MinaBaseSignedCommandMemoStableV1;
300
301    fn pub_key(address: &str) -> manual::NonZeroCurvePoint {
302        let key = mina_signer::PubKey::from_address(address)
303            .unwrap()
304            .into_compressed();
305        let v = generated::NonZeroCurvePointUncompressedStableV1 {
306            x: crate::bigint::BigInt::from(key.x),
307            is_odd: key.is_odd,
308        };
309        v.into()
310    }
311
312    fn tx_hash(
313        from: &str,
314        to: &str,
315        amount: u64,
316        fee: u64,
317        nonce: u32,
318        valid_until: u32,
319    ) -> String {
320        use crate::{number::Number, string::CharString};
321
322        let from = pub_key(from);
323        let to = pub_key(to);
324
325        let v = Number(fee);
326        let v = generated::UnsignedExtendedUInt64Int64ForVersionTagsStableV1(v);
327        let fee = generated::CurrencyFeeStableV1(v);
328
329        let nonce = generated::UnsignedExtendedUInt32StableV1(Number(nonce));
330
331        let valid_until = generated::UnsignedExtendedUInt32StableV1(Number(valid_until));
332
333        let memo = bs58::decode("E4Yks7aARFemZJqucP5eaARRYRthGdzaFjGfXqQRS3UeidsECRBvR")
334            .with_check(Some(0x14))
335            .into_vec()
336            .unwrap()[1..]
337            .to_vec();
338        let v = CharString::from(&memo[..]);
339        let memo = MinaBaseSignedCommandMemoStableV1(v);
340
341        let common = generated::MinaBaseSignedCommandPayloadCommonStableV2 {
342            fee,
343            fee_payer_pk: from.clone(),
344            nonce,
345            valid_until: MinaNumbersGlobalSlotSinceGenesisMStableV1::SinceGenesis(valid_until),
346            memo,
347        };
348
349        let v = Number(amount);
350        let v = generated::UnsignedExtendedUInt64Int64ForVersionTagsStableV1(v);
351        let amount = generated::CurrencyAmountStableV1(v);
352
353        let v = generated::MinaBasePaymentPayloadStableV2 {
354            receiver_pk: to.clone(),
355            amount,
356        };
357        let body = generated::MinaBaseSignedCommandPayloadBodyStableV2::Payment(v);
358
359        let payload = generated::MinaBaseSignedCommandPayloadStableV2 { common, body };
360
361        // Some random signature. hasher should ignore it and use default.
362        let signature = generated::MinaBaseSignatureStableV1(
363            BigInt::binprot_read(&mut &[122; 32][..]).unwrap(),
364            BigInt::binprot_read(&mut &[123; 32][..]).unwrap(),
365        );
366
367        let v = generated::MinaBaseSignedCommandStableV2 {
368            payload,
369            signer: from.clone(),
370            signature: signature.into(),
371        };
372        let v = generated::MinaBaseUserCommandStableV2::SignedCommand(v);
373        dbg!(v.hash().unwrap()).to_string()
374    }
375
376    // #[test]
377    // fn test_tx_hash() {
378    //     let s = "5JuSRViCY1GbnnpExLhoYLkD96vwnA97ZrbE2UFTFzBk9SPLsAyE";
379    //     let hash: TransactionHash = s.parse().unwrap();
380    //     // let bytes = bs58::decode(s).with_check(Some(0x1D)).into_vec().unwrap()[1..].to_vec();
381    //     dbg!(bs58::encode(&hash.0)
382    //         .with_check_version(0x12)
383    //         .into_string());
384    //     panic!();
385    // }
386
387    #[test]
388    #[ignore = "fix expected hash/hasing"]
389    fn test_payment_hash_1() {
390        let expected_hash = "5JthQdVqzEJRLBLALeuwPdbnGhFmCow2bVnkfHGH6vZ7R6fiMf2o";
391        let expected_tx_hash: TransactionHash = expected_hash.parse().unwrap();
392        dbg!(expected_tx_hash);
393
394        assert_eq!(
395            tx_hash(
396                "B62qp3B9VW1ir5qL1MWRwr6ecjC2NZbGr8vysGeme9vXGcFXTMNXb2t",
397                "B62qoieQNrsNKCNTZ6R4D6cib3NxVbkwZaAtRVbfS3ndrb2MkFJ1UVJ",
398                1089541195,
399                89541195,
400                26100,
401                u32::MAX,
402            ),
403            expected_hash
404        )
405    }
406}
407
408fn fp_state_hash_from_fp_hashes(previous_state_hash: Fp, body_hash: Fp) -> Fp {
409    let mut inputs = Inputs::new();
410    inputs.append_field(previous_state_hash);
411    inputs.append_field(body_hash);
412    hash_with_kimchi(&MINA_PROTO_STATE, &inputs.to_fields())
413}
414
415impl StateHash {
416    pub fn from_fp(fp: Fp) -> Self {
417        DataHashLibStateHashStableV1(fp.into()).into()
418    }
419
420    pub fn try_from_hashes(
421        pred_state_hash: &StateHash,
422        body_hash: &MinaBaseStateBodyHashStableV1,
423    ) -> Result<Self, InvalidBigInt> {
424        Ok(Self::from_fp(fp_state_hash_from_fp_hashes(
425            pred_state_hash.to_field().map_err(|_| InvalidBigInt)?,
426            body_hash.to_field()?,
427        )))
428    }
429}
430
431impl LedgerHash {
432    pub fn from_fp(fp: Fp) -> Self {
433        MinaBaseLedgerHash0StableV1(fp.into()).into()
434    }
435
436    pub fn zero() -> Self {
437        MinaBaseLedgerHash0StableV1(BigInt::zero()).into()
438    }
439}
440
441impl PendingCoinbaseHash {
442    pub fn from_fp(fp: Fp) -> Self {
443        MinaBasePendingCoinbaseHashVersionedStableV1(MinaBasePendingCoinbaseHashBuilderStableV1(
444            fp.into(),
445        ))
446        .into()
447    }
448}
449
450impl generated::MinaStateProtocolStateBodyValueStableV2 {
451    // TODO(binier): change return type to `StateBodyHash`
452    pub fn try_hash(&self) -> Result<MinaBaseStateBodyHashStableV1, InvalidBigInt> {
453        let fp = MinaHash::try_hash(self)?;
454        Ok(MinaBaseStateBodyHashStableV1(fp.into()))
455    }
456}
457
458impl generated::MinaStateProtocolStateValueStableV2 {
459    pub fn try_hash(&self) -> Result<StateHash, InvalidBigInt> {
460        Ok(StateHash::from_fp(MinaHash::try_hash(self)?))
461    }
462}
463
464impl generated::MinaBlockHeaderStableV2 {
465    pub fn try_hash(&self) -> Result<StateHash, InvalidBigInt> {
466        self.protocol_state.try_hash()
467    }
468}
469
470impl generated::MinaBlockBlockStableV2 {
471    pub fn try_hash(&self) -> Result<StateHash, InvalidBigInt> {
472        self.header.protocol_state.try_hash()
473    }
474}
475
476impl MinaHash for MinaStateProtocolStateBodyValueStableV2 {
477    fn try_hash(&self) -> Result<mina_curves::pasta::Fp, InvalidBigInt> {
478        let mut inputs = Inputs::new();
479        self.to_input(&mut inputs)?;
480        Ok(hash_with_kimchi(
481            &MINA_PROTO_STATE_BODY,
482            &inputs.to_fields(),
483        ))
484    }
485}
486
487impl MinaHash for MinaStateProtocolStateValueStableV2 {
488    fn try_hash(&self) -> Result<mina_curves::pasta::Fp, InvalidBigInt> {
489        Ok(fp_state_hash_from_fp_hashes(
490            self.previous_state_hash
491                .to_field()
492                .map_err(|_| InvalidBigInt)?,
493            MinaHash::try_hash(&self.body)?,
494        ))
495    }
496}
497
498impl FailableToInputs for MinaStateProtocolStateBodyValueStableV2 {
499    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
500        let MinaStateProtocolStateBodyValueStableV2 {
501            genesis_state_hash,
502            blockchain_state,
503            consensus_state,
504            constants,
505        } = self;
506
507        constants.to_input(inputs)?;
508        genesis_state_hash.to_input(inputs)?;
509        blockchain_state.to_input(inputs)?;
510        consensus_state.to_input(inputs)?;
511        Ok(())
512    }
513}
514
515impl FailableToInputs for MinaBaseProtocolConstantsCheckedValueStableV1 {
516    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
517        let MinaBaseProtocolConstantsCheckedValueStableV1 {
518            k,
519            slots_per_epoch,
520            slots_per_sub_window,
521            grace_period_slots,
522            delta,
523            genesis_state_timestamp,
524        } = self;
525
526        k.to_input(inputs)?;
527        delta.to_input(inputs)?;
528        slots_per_epoch.to_input(inputs)?;
529        slots_per_sub_window.to_input(inputs)?;
530        grace_period_slots.to_input(inputs)?;
531        genesis_state_timestamp.to_input(inputs)?;
532        Ok(())
533    }
534}
535
536impl FailableToInputs for MinaStateBlockchainStateValueStableV2 {
537    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
538        let MinaStateBlockchainStateValueStableV2 {
539            staged_ledger_hash,
540            genesis_ledger_hash,
541            ledger_proof_statement,
542            timestamp,
543            body_reference,
544        } = self;
545
546        staged_ledger_hash.to_input(inputs)?;
547        genesis_ledger_hash.to_input(inputs)?;
548        ledger_proof_statement.to_input(inputs)?;
549        timestamp.to_input(inputs)?;
550        body_reference.to_input(inputs)?;
551
552        Ok(())
553    }
554}
555
556impl FailableToInputs for ConsensusProofOfStakeDataConsensusStateValueStableV2 {
557    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
558        let ConsensusProofOfStakeDataConsensusStateValueStableV2 {
559            blockchain_length,
560            epoch_count,
561            min_window_density,
562            sub_window_densities,
563            last_vrf_output,
564            total_currency,
565            curr_global_slot_since_hard_fork,
566            global_slot_since_genesis,
567            staking_epoch_data,
568            next_epoch_data,
569            has_ancestor_in_same_checkpoint_window,
570            block_stake_winner,
571            block_creator,
572            coinbase_receiver,
573            supercharge_coinbase,
574        } = self;
575        blockchain_length.to_input(inputs)?;
576        epoch_count.to_input(inputs)?;
577        min_window_density.to_input(inputs)?;
578        sub_window_densities.to_input(inputs)?;
579        last_vrf_output.to_input(inputs)?;
580        total_currency.to_input(inputs)?;
581        curr_global_slot_since_hard_fork.to_input(inputs)?;
582        global_slot_since_genesis.to_input(inputs)?;
583        has_ancestor_in_same_checkpoint_window.to_input(inputs)?;
584        supercharge_coinbase.to_input(inputs)?;
585        staking_epoch_data.to_input(inputs)?;
586        next_epoch_data.to_input(inputs)?;
587        block_stake_winner.to_input(inputs)?;
588        block_creator.to_input(inputs)?;
589        coinbase_receiver.to_input(inputs)?;
590        Ok(())
591    }
592}
593
594impl FailableToInputs for MinaBaseStagedLedgerHashStableV1 {
595    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
596        let MinaBaseStagedLedgerHashStableV1 {
597            non_snark,
598            pending_coinbase_hash,
599        } = self;
600        non_snark.to_input(inputs)?;
601        pending_coinbase_hash.to_input(inputs)?;
602        Ok(())
603    }
604}
605
606impl FailableToInputs for MinaBaseStagedLedgerHashNonSnarkStableV1 {
607    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
608        inputs.append_bytes(self.sha256().as_ref());
609        Ok(())
610    }
611}
612
613impl FailableToInputs for MinaStateBlockchainStateValueStableV2LedgerProofStatement {
614    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
615        let MinaStateBlockchainStateValueStableV2LedgerProofStatement {
616            source,
617            target,
618            connecting_ledger_left,
619            connecting_ledger_right,
620            supply_increase,
621            fee_excess,
622            sok_digest: _,
623        } = self;
624        source.to_input(inputs)?;
625        target.to_input(inputs)?;
626        connecting_ledger_left.to_input(inputs)?;
627        connecting_ledger_right.to_input(inputs)?;
628        supply_increase.to_input(inputs)?;
629        fee_excess.to_input(inputs)?;
630        Ok(())
631    }
632}
633
634impl FailableToInputs for ConsensusBodyReferenceStableV1 {
635    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
636        inputs.append_bytes(self.as_ref());
637        Ok(())
638    }
639}
640
641impl FailableToInputs for ConsensusVrfOutputTruncatedStableV1 {
642    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
643        let vrf: &[u8] = self.as_ref();
644        inputs.append_bytes(&vrf[..31]);
645        // Ignore the last 3 bits
646        let last_byte = vrf[31];
647        for bit in [1, 2, 4, 8, 16] {
648            inputs.append_bool(last_byte & bit != 0);
649        }
650        Ok(())
651    }
652}
653
654impl FailableToInputs for ConsensusGlobalSlotStableV1 {
655    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
656        let ConsensusGlobalSlotStableV1 {
657            slot_number,
658            slots_per_epoch,
659        } = self;
660        slot_number.to_input(inputs)?;
661        slots_per_epoch.to_input(inputs)?;
662        Ok(())
663    }
664}
665
666impl FailableToInputs for ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 {
667    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
668        let ConsensusProofOfStakeDataEpochDataStakingValueVersionedValueStableV1 {
669            ledger,
670            seed,
671            start_checkpoint,
672            lock_checkpoint,
673            epoch_length,
674        } = self;
675        seed.to_input(inputs)?;
676        start_checkpoint.to_input(inputs)?;
677        epoch_length.to_input(inputs)?;
678        ledger.to_input(inputs)?;
679        lock_checkpoint.to_input(inputs)?;
680        Ok(())
681    }
682}
683
684impl FailableToInputs for ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 {
685    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
686        let ConsensusProofOfStakeDataEpochDataNextValueVersionedValueStableV1 {
687            ledger,
688            seed,
689            start_checkpoint,
690            lock_checkpoint,
691            epoch_length,
692        } = self;
693        seed.to_input(inputs)?;
694        start_checkpoint.to_input(inputs)?;
695        epoch_length.to_input(inputs)?;
696        ledger.to_input(inputs)?;
697        lock_checkpoint.to_input(inputs)?;
698        Ok(())
699    }
700}
701
702impl FailableToInputs for NonZeroCurvePointUncompressedStableV1 {
703    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
704        let NonZeroCurvePointUncompressedStableV1 { x, is_odd } = self;
705        x.to_input(inputs)?;
706        is_odd.to_input(inputs)?;
707        Ok(())
708    }
709}
710
711impl FailableToInputs for MinaStateBlockchainStateValueStableV2LedgerProofStatementSource {
712    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
713        let MinaStateBlockchainStateValueStableV2LedgerProofStatementSource {
714            first_pass_ledger,
715            second_pass_ledger,
716            pending_coinbase_stack,
717            local_state,
718        } = self;
719        first_pass_ledger.to_input(inputs)?;
720        second_pass_ledger.to_input(inputs)?;
721        pending_coinbase_stack.to_input(inputs)?;
722        local_state.to_input(inputs)?;
723        Ok(())
724    }
725}
726
727impl FailableToInputs for SignedAmount {
728    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
729        let SignedAmount { magnitude, sgn } = self;
730        magnitude.to_input(inputs)?;
731        sgn.to_input(inputs)?;
732        Ok(())
733    }
734}
735
736impl FailableToInputs for MinaBaseFeeExcessStableV1 {
737    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
738        let MinaBaseFeeExcessStableV1(left, right) = self;
739        left.to_input(inputs)?;
740        right.to_input(inputs)?;
741        Ok(())
742    }
743}
744
745impl FailableToInputs for TokenFeeExcess {
746    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
747        let TokenFeeExcess { token, amount } = self;
748        token.to_input(inputs)?;
749        amount.to_input(inputs)?;
750        Ok(())
751    }
752}
753
754impl FailableToInputs for MinaBaseEpochLedgerValueStableV1 {
755    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
756        let MinaBaseEpochLedgerValueStableV1 {
757            hash,
758            total_currency,
759        } = self;
760        hash.to_input(inputs)?;
761        total_currency.to_input(inputs)?;
762        Ok(())
763    }
764}
765
766impl FailableToInputs for MinaBasePendingCoinbaseStackVersionedStableV1 {
767    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
768        let MinaBasePendingCoinbaseStackVersionedStableV1 { data, state } = self;
769        data.to_input(inputs)?;
770        state.to_input(inputs)?;
771        Ok(())
772    }
773}
774
775impl FailableToInputs for MinaBasePendingCoinbaseStateStackStableV1 {
776    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
777        let MinaBasePendingCoinbaseStateStackStableV1 { init, curr } = self;
778        init.to_input(inputs)?;
779        curr.to_input(inputs)?;
780        Ok(())
781    }
782}
783
784impl FailableToInputs for MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 {
785    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
786        let MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 {
787            stack_frame,
788            call_stack,
789            transaction_commitment,
790            full_transaction_commitment,
791            excess,
792            supply_increase,
793            ledger,
794            success,
795            account_update_index,
796            failure_status_tbl: _,
797            will_succeed,
798        } = self;
799        stack_frame.to_input(inputs)?;
800        call_stack.to_input(inputs)?;
801        transaction_commitment.to_input(inputs)?;
802        full_transaction_commitment.to_input(inputs)?;
803        excess.to_input(inputs)?;
804        supply_increase.to_input(inputs)?;
805        ledger.to_input(inputs)?;
806        account_update_index.to_input(inputs)?;
807        success.to_input(inputs)?;
808        will_succeed.to_input(inputs)?;
809        Ok(())
810    }
811}
812
813impl FailableToInputs for SgnStableV1 {
814    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
815        inputs.append_bool(self == &SgnStableV1::Pos);
816        Ok(())
817    }
818}
819
820impl FailableToInputs for MinaNumbersGlobalSlotSinceGenesisMStableV1 {
821    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
822        match self {
823            MinaNumbersGlobalSlotSinceGenesisMStableV1::SinceGenesis(v) => v.to_input(inputs),
824        }
825    }
826}
827
828impl FailableToInputs for MinaStateBlockchainStateValueStableV2SignedAmount {
829    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
830        let MinaStateBlockchainStateValueStableV2SignedAmount { magnitude, sgn } = self;
831        magnitude.to_input(inputs)?;
832        sgn.to_input(inputs)?;
833        Ok(())
834    }
835}
836
837impl FailableToInputs for MinaNumbersGlobalSlotSinceHardForkMStableV1 {
838    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
839        match self {
840            MinaNumbersGlobalSlotSinceHardForkMStableV1::SinceHardFork(v) => v.to_input(inputs),
841        }
842    }
843}
844
845impl FailableToInputs for MinaNumbersGlobalSlotSpanStableV1 {
846    fn to_input(&self, inputs: &mut Inputs) -> Result<(), InvalidBigInt> {
847        match self {
848            MinaNumbersGlobalSlotSpanStableV1::GlobalSlotSpan(v) => v.to_input(inputs),
849        }
850    }
851}
852
853#[cfg(test)]
854mod hash_tests {
855    use binprot::BinProtRead;
856
857    use crate::v2::{
858        MinaBaseZkappCommandTStableV1WireStableV1, MinaStateProtocolStateValueStableV2,
859    };
860
861    #[test]
862    #[ignore = "fix expected hash/hasing"]
863    fn state_hash() {
864        const HASH: &str = "3NKpXp2SXWGC3XHnAJYjGtNcbq8tzossqj6kK4eGr6mSyJoFmpxR";
865        const JSON: &str = include_str!("../../tests/files/v2/state/617-3NKpXp2SXWGC3XHnAJYjGtNcbq8tzossqj6kK4eGr6mSyJoFmpxR.json");
866
867        let state: MinaStateProtocolStateValueStableV2 = serde_json::from_str(JSON).unwrap();
868        let hash = state.try_hash().unwrap();
869        let expected_hash = serde_json::from_value(serde_json::json!(HASH)).unwrap();
870        assert_eq!(hash, expected_hash)
871    }
872
873    #[test]
874    fn test_zkapp_with_proof_auth_hash() {
875        // expected: 5JtkEP5AugQKKQAk3YKFxxUDggWf8AiAYyCQy49t2kLHRgPqcP8o
876        // MinaBaseZkappCommandTStableV1WireStableV1
877        //
878        let expected_hash = "5JtkEP5AugQKKQAk3YKFxxUDggWf8AiAYyCQy49t2kLHRgPqcP8o".to_string();
879        let bytes = include_bytes!("../../../../tests/files/zkapps/with_proof_auth.bin");
880        let zkapp =
881            MinaBaseZkappCommandTStableV1WireStableV1::binprot_read(&mut bytes.as_slice()).unwrap();
882        let hash = zkapp.hash().unwrap().to_string();
883
884        assert_eq!(expected_hash, hash);
885    }
886
887    #[test]
888
889    fn test_zkapp_with_sig_auth_hash() {
890        let expected_hash = "5JvQ6xQeGgCTe2d4KpCsJ97yK61mNRZHixJxPbKTppY1qSGgtj6t".to_string();
891        let bytes = include_bytes!("../../../../tests/files/zkapps/with_sig_auth.bin");
892        let zkapp =
893            MinaBaseZkappCommandTStableV1WireStableV1::binprot_read(&mut bytes.as_slice()).unwrap();
894        let hash = zkapp.hash().unwrap().to_string();
895
896        assert_eq!(expected_hash, hash);
897    }
898}