risc0_binfmt/
povw.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Core types for function specific to proof of verifiable work (PoVW).
16
17extern crate alloc;
18
19use alloc::{collections::VecDeque, vec::Vec};
20
21use borsh::{BorshDeserialize, BorshSerialize};
22use ruint::aliases::{U160, U256, U64};
23use serde::{Deserialize, Serialize};
24
25#[cfg(feature = "rand")]
26use rand::{
27    distr::{Distribution, StandardUniform},
28    Rng,
29};
30
31use crate::DecodeError;
32
33/// A 160-bit identifier for a PoVW work log.
34///
35/// Each prover maintains one or more work logs, with each log having a unique identifier.
36/// Each work log is guaranteed to have a distinct range of nonces, allowing the used nonces
37/// from each log to be tracked separately.
38pub type PovwLogId = U160;
39
40/// Globally unique identifier for a proving job.
41///
42/// A proving job represents a single continuation (sequence of segments) being proven.
43/// The job ID combines a work log identifier with a job number to create a globally
44/// unique identifier that can generate nonces for each segment in the job.
45#[derive(
46    Copy,
47    Clone,
48    Debug,
49    Default,
50    Serialize,
51    Deserialize,
52    BorshSerialize,
53    BorshDeserialize,
54    PartialEq,
55    Eq,
56)]
57pub struct PovwJobId {
58    /// The work log that this job belongs to.
59    pub log: PovwLogId,
60    /// Job number within the work log.
61    pub job: u64,
62}
63
64impl PovwJobId {
65    /// Creates a nonce for a specific segment within this job.
66    pub fn nonce(self, segment_index: u32) -> PovwNonce {
67        PovwNonce {
68            log: self.log,
69            job: self.job,
70            segment: segment_index,
71        }
72    }
73
74    /// Serializes the job ID to a byte array in little-endian format.
75    pub fn to_bytes(self) -> [u8; U160::BYTES + U64::BYTES] {
76        [
77            self.job.to_le_bytes().as_slice(),
78            self.log.to_le_bytes::<{ U160::BYTES }>().as_slice(),
79        ]
80        .concat()
81        .try_into()
82        .unwrap()
83    }
84
85    /// Deserializes a job ID from a byte array in little-endian format.
86    pub fn from_bytes(bytes: [u8; U160::BYTES + U64::BYTES]) -> Self {
87        Self {
88            job: u64::from_le_bytes(bytes[..U64::BYTES].try_into().unwrap()),
89            log: U160::from_le_bytes::<{ U160::BYTES }>(bytes[U64::BYTES..].try_into().unwrap()),
90        }
91    }
92}
93
94impl From<(PovwLogId, u64)> for PovwJobId {
95    fn from((log, job): (PovwLogId, u64)) -> Self {
96        Self { log, job }
97    }
98}
99
100impl TryFrom<&[u8]> for PovwJobId {
101    type Error = core::array::TryFromSliceError;
102
103    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
104        Ok(Self::from_bytes(value.try_into()?))
105    }
106}
107
108#[cfg(feature = "rand")]
109impl Distribution<PovwJobId> for StandardUniform {
110    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> PovwJobId {
111        PovwJobId {
112            log: rng.random(),
113            job: rng.random(),
114        }
115    }
116}
117
118/// A 256-bit unique nonce for Proof of Verifiable Work.
119///
120/// Each nonce uniquely identifies a single segment proof and prevents double-counting
121/// of work. The nonce has a structured format combining a work log ID, job number,
122/// and segment index.
123#[derive(
124    Copy,
125    Clone,
126    Debug,
127    Default,
128    Serialize,
129    Deserialize,
130    BorshSerialize,
131    BorshDeserialize,
132    PartialEq,
133    Eq,
134)]
135pub struct PovwNonce {
136    /// Work log identifier.
137    pub log: PovwLogId,
138    /// Job number within the work log.
139    pub job: u64,
140    /// Segment index within the job.
141    pub segment: u32,
142}
143
144impl PovwNonce {
145    /// A zero nonce constant.
146    pub const ZERO: Self = Self {
147        log: PovwLogId::ZERO,
148        job: 0,
149        segment: 0,
150    };
151
152    /// Converts the nonce to a 256-bit byte array in little-endian format.
153    pub fn to_bytes(self) -> [u8; U256::BYTES] {
154        <U256 as From<Self>>::from(self).to_le_bytes()
155    }
156
157    /// Creates a nonce from a 256-bit byte array in little-endian format.
158    pub fn from_bytes(bytes: [u8; U256::BYTES]) -> Self {
159        U256::from_le_bytes::<{ U256::BYTES }>(bytes).into()
160    }
161
162    /// Converts the nonce to its U256 representation.
163    pub fn to_u256(self) -> U256 {
164        (self.log.to::<U256>() << 96) | (U256::from(self.job) << 32) | U256::from(self.segment)
165    }
166
167    /// Converts the nonce to an array of 8 u32 values.
168    pub fn to_u32s(self) -> [u32; 8] {
169        let mut u32s = bytemuck::cast::<_, [u32; 8]>(self.to_bytes());
170        // Bytes are little-endian, so on a big-endian machine, they need to be reversed.
171        for x in u32s.iter_mut() {
172            *x = u32::from_le(*x);
173        }
174        u32s
175    }
176
177    /// Converts the nonce to an array of 16 u16 values.
178    pub fn to_u16s(self) -> [u16; 16] {
179        let mut u16s = bytemuck::cast::<_, [u16; 16]>(self.to_bytes());
180        // Bytes are little-endian, so on a big-endian machine, they need to be reversed.
181        for x in u16s.iter_mut() {
182            *x = u16::from_le(*x);
183        }
184        u16s
185    }
186
187    /// Creates a nonce from an array of 16 u16 values.
188    pub fn from_u16s(mut u16s: [u16; 16]) -> Self {
189        // Bytes need to be little-endian, so on a big-endian machine, they need to be reversed.
190        for x in u16s.iter_mut() {
191            *x = u16::from_le(*x);
192        }
193        Self::from_bytes(bytemuck::cast(u16s))
194    }
195
196    /// Encodes the nonce to a seal buffer.
197    pub fn encode_to_seal(&self, buf: &mut Vec<u32>) {
198        buf.extend(self.to_u16s().into_iter().map(u32::from));
199    }
200
201    /// Decodes a nonce from a seal buffer.
202    pub fn decode_from_seal(buf: &mut VecDeque<u32>) -> Result<Self, DecodeError> {
203        if buf.len() < 16 {
204            return Err(DecodeError::EndOfStream);
205        }
206        fn u16_from_u32(x: u32) -> Result<u16, DecodeError> {
207            x.try_into().map_err(|_| DecodeError::OutOfRange)
208        }
209        Ok(Self::from_u16s(
210            buf.drain(..16)
211                .map(u16_from_u32)
212                .collect::<Result<Vec<_>, _>>()?
213                .try_into()
214                .unwrap(),
215        ))
216    }
217}
218
219impl From<PovwNonce> for U256 {
220    /// Convert a [PovwNonce] to its [U256] representation.
221    fn from(value: PovwNonce) -> Self {
222        value.to_u256()
223    }
224}
225
226impl From<U256> for PovwNonce {
227    /// Convert a [U256] to a [PovwNonce].
228    ///
229    /// All [U256] can be mapped to a unique PoVW nonce.
230    fn from(value: U256) -> Self {
231        Self {
232            log: (value >> 96usize).to(),
233            job: ((value >> 32usize) & U256::from(u64::MAX)).to(),
234            segment: (value & U256::from(u32::MAX)).to(),
235        }
236    }
237}
238
239impl TryFrom<&[u8]> for PovwNonce {
240    type Error = core::array::TryFromSliceError;
241
242    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
243        Ok(Self::from_bytes(value.try_into()?))
244    }
245}
246
247impl TryFrom<Vec<u8>> for PovwNonce {
248    type Error = core::array::TryFromSliceError;
249
250    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
251        value.as_slice().try_into()
252    }
253}
254
255#[cfg(feature = "rand")]
256impl Distribution<PovwNonce> for StandardUniform {
257    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> PovwNonce {
258        PovwNonce {
259            log: rng.random(),
260            job: rng.random(),
261            segment: rng.random(),
262        }
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::{PovwJobId, PovwNonce};
269
270    #[test]
271    fn test_povw_job_id_round_trip() {
272        let original: PovwJobId = rand::random();
273        let bytes = original.to_bytes();
274        let reconstructed = PovwJobId::from_bytes(bytes);
275        assert_eq!(original, reconstructed);
276    }
277
278    #[test]
279    fn test_povw_nonce_bytes_round_trip() {
280        let original: PovwNonce = rand::random();
281        let bytes = original.to_bytes();
282        let reconstructed = PovwNonce::from_bytes(bytes);
283        assert_eq!(original, reconstructed);
284    }
285
286    #[test]
287    fn test_povw_nonce_u16s_round_trip() {
288        let original: PovwNonce = rand::random();
289        let u16s = original.to_u16s();
290        let reconstructed = PovwNonce::from_u16s(u16s);
291        assert_eq!(original, reconstructed);
292    }
293}