pbc_zk_core/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
#![doc = include_str!("../README.md")]
mod sbi;
mod secret_binary;
use pbc_abi::abi_model::TypeSpec;
use std::io::{Read, Write};
use pbc_abi::{
create_type_spec::{NamedTypeLookup, NamedTypeSpecs},
CreateTypeSpec,
};
pub use sbi::FromToBits;
#[cfg(doc)]
pub use sbi::Sbi;
#[cfg(not(doc))]
use sbi::Sbi;
#[allow(unused_imports)] // Reexport
pub use secret_binary::*;
/// A secret-shared [`bool`] value.
pub type Sbu1 = bool;
/// A secret-shared [`bool`] value. Deprecated, prefer [`Sbu1`].
#[deprecated(note = "Use `pbc_zk::Sbu1` instead. Change made to improve type consistency.")]
pub type Sbi1 = Sbu1;
macro_rules! sbi_impl {
($SelfT:ident, $InnerT:ty) => {
#[doc = concat!("A [`", stringify!($InnerT), "`] secret-shared using a binary field.")]
///
#[doc = concat!("Can be used similarly to normal/public [`", stringify!($InnerT), "`] values, with the restriction that any public [`", stringify!($InnerT), "`] must be upgraded to an [`", stringify!($SelfT), "`] before usage.")]
///
/// For example:
///
/// ```
/// # use pbc_zk_core::*;
#[doc = concat!("let x = ", stringify!($SelfT), "::from(1);")]
#[doc = concat!("let y = ", stringify!($SelfT), "::from(9);")]
#[doc = concat!("assert_eq!(x + y, ", stringify!($SelfT), "::from(10));")]
/// ```
///
/// ## Information Flow Control
///
/// Variables in REAL must satisfy some [information
/// flow](https://en.wikipedia.org/wiki/Information_flow_(information_theory))
/// requirements, which states that it must not be possible to infer the value of
/// a secret variable. This basic requirement then results in a bunch of type constraints:
///
/// - `Sbi` values cannot be downcasted to public integers.
/// - While-statements on `Sbi` values is impossible.
/// - If-statements on `Sbi` values has some additional limitations in the branches:
/// * Can only assign secret-shared values.
/// * Cannot call functions.
/// * Cannot use while-statements.
/// * Cannot use public if-statements.
///
/// Here are some examples of disallowed data flows:
///
/// ```compile_fail
/// # use pbc_zk_core::*;
#[doc = concat!("let x: ", stringify!($SelfT), " = ", stringify!($SelfT), "::from(9);")]
#[doc = concat!("let y: ", stringify!($InnerT), " = x; // Fails: Secrets cannot become public")]
#[doc = concat!("let z: ", stringify!($SelfT), " = x + 10; // Fails: Missing cast")]
/// ```
///
/// ## Representation
///
/// The true value of instances of this type is secret-shared across several computation nodes,
/// and certain operations require network communication, which may result in a significantly slower
/// operation than on their public equivalents.
///
#[doc = concat!("[`", stringify!($SelfT), "`]")]
/// uses a binary-field secret-sharing, which allows operations on bits to be done
/// efficiently, but arithmetic operations needs to be implemented using the bit
/// operations, which can be slow.
///
/// Here is a rough overview of the theoretic performance. `N` is the type length in bits. Lower numbers are better:
///
/// | Operation | Rounds<br>(#Networking Packets) | Multiplications<br>(Size of Networking Packets)|
/// | --------- | ------ | --------------- |
/// | [`BitXor`](std::ops::BitXor) | `0` | `0` |
/// | [`Shl`](std::ops::Shl) / [`Shr`](std::ops::Shr) | `0` | `0` |
/// | [`BitAnd`](std::ops::BitAnd) | `1` | `N` |
/// | [`BitOr`](std::ops::BitOr) | `1` | `N` |
/// | If-expression return value | `1` | `2*N` |
/// | [`PartialEq`](PartialEq) / [`Eq`](Eq) | `log2(N)` | `N-1` |
/// | [`Add`](std::ops::Add) / [`Sub`](std::ops::BitXor) | `N` | `2*N` |
/// | [`Ord`](Ord) (lt, gt) | `N` | `2*N` |
/// | [`Mul`](std::ops::Mul) | `N` squared | `2*N` squared |
///
/// ### Example
///
/// Consider the following program:
///
/// ```rust
/// # use pbc_zk_core::*;
/// fn max(a: Sbi32, b: Sbi32) -> Sbi32 {
/// if a < b { b } else { a }
/// }
/// ```
///
/// One execution of `max` would involve `33` rounds (`32` from `<` and `1` from `if`) and `128` multiplications (`64` from `<` and `64` from `if`).
pub type $SelfT = Sbi<$InnerT>;
};
}
sbi_impl! { Sbi8, i8 }
sbi_impl! { Sbi16, i16 }
sbi_impl! { Sbi32, i32 }
sbi_impl! { Sbi64, i64 }
sbi_impl! { Sbi128, i128 }
sbi_impl! { Sbu8, u8 }
sbi_impl! { Sbu16, u16 }
sbi_impl! { Sbu32, u32 }
sbi_impl! { Sbu64, u64 }
sbi_impl! { Sbu128, u128 }
/// Required for secret-shared values.
/// Secret variables are serialized like their public counterparts using the
/// [State serialization format](https://partisiablockchain.gitlab.io/documentation/smart-contracts/smart-contract-binary-formats.html#state-binary-format).
pub trait SecretBinary {
/// Deserialization method for a secret.
fn secret_read_from<T: Read>(reader: &mut T) -> Self;
/// Serialization method for a secret.
fn secret_write_to<T: Write>(&self, writer: &mut T) -> std::io::Result<()>;
}
pub use crate::SecretBinary as Secret;
/// Required for secret-shared values. Used to determine the size of secret-shared inputs.
pub trait SecretBinaryFixedSize {
/// The bitsize of the type.
const BITS: u32;
}
macro_rules! impl_secretbinary_delegate_to_read_write_state {
($($type:ty)*) => {
$(
#[doc = "Implementation of the [`SecretBinary`] trait for [`"]
#[doc = stringify!($type)]
#[doc = "`]."]
impl SecretBinary for $type {
fn secret_read_from<ReadT: Read>(reader: &mut ReadT) -> Self {
<Self as pbc_traits::ReadWriteState>::state_read_from(reader)
}
fn secret_write_to<WriteT: Write>(&self, writer: &mut WriteT) -> std::io::Result<()> {
<Self as pbc_traits::ReadWriteState>::state_write_to(self, writer)
}
}
impl SecretBinaryFixedSize for $type {
const BITS: u32 = <$type>::BITS as u32;
}
)*
}
}
impl_secretbinary_delegate_to_read_write_state! {
i8
i16
i32
i64
i128
u8
u16
u32
u64
u128
}
/// The output is n implementations of [`CreateTypeSpec`] that simply write the type as a string
/// and fill the ordinal in the [`CreateTypeSpec::__ty_spec_write`] vector output.
macro_rules! impl_createtypespec_for_type {
($($type:ty, $val:ident)*) => {
$(
#[doc = "Implementation of the [`CreateTypeSpec`] trait for [`"]
#[doc = stringify!($type)]
#[doc = "`]."]
impl CreateTypeSpec for $type {
#[doc = concat!("Constant string [`", stringify!($type), "`].")]
fn __ty_name() -> String {
format!("{}", stringify!($type).to_string())
}
#[doc = concat!("Ordinal is `", stringify!($val), "`, as defined in [ABI Spec](https://partisiablockchain.gitlab.io/documentation/smart-contracts/smart-contract-binary-formats.html).")]
fn __ty_identifier() -> String {
Self::__ty_name()
}
fn __ty_generate_spec(
_named_type_index_lookup: &mut NamedTypeLookup,
_named_type_specs: &mut NamedTypeSpecs,)
-> TypeSpec {
TypeSpec::$val
}
}
)*
}
}
// Sbi types are mapped to their public counterparts.
impl_createtypespec_for_type!(
Sbu8, U8
Sbu16, U16
Sbu32, U32
Sbu64, U64
Sbu128, U128
Sbi8, I8
Sbi16, I16
Sbi32, I32
Sbi64, I64
Sbi128, I128
);
/// Implementation of [`SecretBinary`] for arrays of arbitrary sizes and types.
impl<const LEN: usize, ElementT: SecretBinary + Sized> SecretBinary for [ElementT; LEN] {
fn secret_read_from<T: Read>(reader: &mut T) -> Self {
let mut data: [std::mem::MaybeUninit<ElementT>; LEN] =
unsafe { std::mem::MaybeUninit::uninit().assume_init() };
for element_addr in &mut data[..] {
element_addr.write(<ElementT as SecretBinary>::secret_read_from(reader));
}
data.map(|x| unsafe { x.assume_init() })
}
fn secret_write_to<T: Write>(&self, writer: &mut T) -> std::io::Result<()> {
for elem in self {
<ElementT as SecretBinary>::secret_write_to(elem, writer)?;
}
Ok(())
}
}
impl SecretBinary for Vec<Sbu8> {
fn secret_read_from<T: Read>(_reader: &mut T) -> Self {
panic!("Deserialization of Vec<Sbu8> not supported.")
}
fn secret_write_to<T: Write>(&self, _writer: &mut T) -> std::io::Result<()> {
panic!("Serialization of Vec<Sbu8> not supported.")
}
}
impl SecretBinary for Vec<Sbi8> {
fn secret_read_from<T: Read>(_reader: &mut T) -> Self {
panic!("Deserialization of Vec<Sbi8> not supported.")
}
fn secret_write_to<T: Write>(&self, _writer: &mut T) -> std::io::Result<()> {
panic!("Serialization of Vec<Sbi8> not supported.")
}
}