mirror of
https://github.com/arnaucube/miden-crypto.git
synced 2026-01-11 16:41:29 +01:00
Compare commits
6 Commits
v0.11.0
...
al-falcon-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2532fd843 | ||
|
|
9c10f21dfd | ||
|
|
3a3bd61152 | ||
|
|
6e2657dae1 | ||
|
|
2b6d8d11ae | ||
|
|
d7868c5863 |
@@ -1,3 +1,7 @@
|
|||||||
|
## 0.9.3 (2024-04-24)
|
||||||
|
|
||||||
|
* Added `RpxRandomCoin` struct (#307).
|
||||||
|
|
||||||
## 0.9.2 (2024-04-21)
|
## 0.9.2 (2024-04-21)
|
||||||
|
|
||||||
* Implemented serialization for the `Smt` struct (#304).
|
* Implemented serialization for the `Smt` struct (#304).
|
||||||
|
|||||||
38
Cargo.lock
generated
38
Cargo.lock
generated
@@ -443,9 +443,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.30"
|
version = "0.1.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2"
|
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -506,7 +506,7 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miden-crypto"
|
name = "miden-crypto"
|
||||||
version = "0.9.2"
|
version = "0.9.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake3",
|
"blake3",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -786,9 +786,9 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.32"
|
version = "0.38.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
|
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -1029,37 +1029,15 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu",
|
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "miden-crypto"
|
name = "miden-crypto"
|
||||||
version = "0.9.2"
|
version = "0.9.3"
|
||||||
description = "Miden Cryptographic primitives"
|
description = "Miden Cryptographic primitives"
|
||||||
authors = ["miden contributors"]
|
authors = ["miden contributors"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/0xPolygonMiden/crypto"
|
repository = "https://github.com/0xPolygonMiden/crypto"
|
||||||
documentation = "https://docs.rs/miden-crypto/0.9.2"
|
documentation = "https://docs.rs/miden-crypto/0.9.3"
|
||||||
categories = ["cryptography", "no-std"]
|
categories = ["cryptography", "no-std"]
|
||||||
keywords = ["miden", "crypto", "hash", "merkle"]
|
keywords = ["miden", "crypto", "hash", "merkle"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ For the above signatures, key generation, signing, and signature verification ar
|
|||||||
[Pseudo random element generator module](./src/rand/) provides a set of traits and data structures that facilitate generating pseudo-random elements in the context of Miden VM and Miden rollup. The module currently includes:
|
[Pseudo random element generator module](./src/rand/) provides a set of traits and data structures that facilitate generating pseudo-random elements in the context of Miden VM and Miden rollup. The module currently includes:
|
||||||
|
|
||||||
* `FeltRng`: a trait for generating random field elements and random 4 field elements.
|
* `FeltRng`: a trait for generating random field elements and random 4 field elements.
|
||||||
* `RpoRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait.
|
* `RpoRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait using RPO hash function.
|
||||||
|
* `RpxRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait using RPX hash function.
|
||||||
|
|
||||||
## Crate features
|
## Crate features
|
||||||
This crate can be compiled with the following features:
|
This crate can be compiled with the following features:
|
||||||
|
|||||||
@@ -9,46 +9,3 @@ pub use public_key::{PubKeyPoly, PublicKey};
|
|||||||
|
|
||||||
mod secret_key;
|
mod secret_key;
|
||||||
pub use secret_key::SecretKey;
|
pub use secret_key::SecretKey;
|
||||||
|
|
||||||
// TESTS
|
|
||||||
// ================================================================================================
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{dsa::rpo_falcon512::SecretKey, Word, ONE};
|
|
||||||
use rand::SeedableRng;
|
|
||||||
use rand_chacha::ChaCha20Rng;
|
|
||||||
use winter_math::FieldElement;
|
|
||||||
use winter_utils::{Deserializable, Serializable};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_falcon_verification() {
|
|
||||||
let seed = [0_u8; 32];
|
|
||||||
let mut rng = ChaCha20Rng::from_seed(seed);
|
|
||||||
|
|
||||||
// generate random keys
|
|
||||||
let sk = SecretKey::with_rng(&mut rng);
|
|
||||||
let pk = sk.public_key();
|
|
||||||
|
|
||||||
// test secret key serialization/deserialization
|
|
||||||
let mut buffer = vec![];
|
|
||||||
sk.write_into(&mut buffer);
|
|
||||||
let sk_deserialized = SecretKey::read_from_bytes(&buffer).unwrap();
|
|
||||||
assert_eq!(sk.short_lattice_basis(), sk_deserialized.short_lattice_basis());
|
|
||||||
|
|
||||||
// sign a random message
|
|
||||||
let message: Word = [ONE; 4];
|
|
||||||
let signature = sk.sign_with_rng(message, &mut rng);
|
|
||||||
|
|
||||||
// make sure the signature verifies correctly
|
|
||||||
assert!(pk.verify(message, &signature));
|
|
||||||
|
|
||||||
// a signature should not verify against a wrong message
|
|
||||||
let message2: Word = [ONE.double(); 4];
|
|
||||||
assert!(!pk.verify(message2, &signature));
|
|
||||||
|
|
||||||
// a signature should not verify against a wrong public key
|
|
||||||
let sk2 = SecretKey::with_rng(&mut rng);
|
|
||||||
assert!(!sk2.public_key().verify(message, &signature))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ impl SecretKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Given a short basis [[g, -f], [G, -F]], computes the normalized LDL tree i.e., Falcon tree.
|
/// Given a short basis [[g, -f], [G, -F]], computes the normalized LDL tree i.e., Falcon tree.
|
||||||
fn from_short_lattice_basis(basis: ShortLatticeBasis) -> SecretKey {
|
pub(crate) fn from_short_lattice_basis(basis: ShortLatticeBasis) -> SecretKey {
|
||||||
// FFT each polynomial of the short basis.
|
// FFT each polynomial of the short basis.
|
||||||
let basis_fft = to_complex_fft(&basis);
|
let basis_fft = to_complex_fft(&basis);
|
||||||
// compute the Gram matrix.
|
// compute the Gram matrix.
|
||||||
@@ -196,6 +196,98 @@ impl SecretKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HELPER METHODS FOR TESTING
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Signs a message with the secret key relying on the provided randomness generator.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn sign_with_rng_testing<R: Rng>(
|
||||||
|
&self,
|
||||||
|
message: &[u8],
|
||||||
|
rng: &mut R,
|
||||||
|
skip_bytes: usize,
|
||||||
|
) -> Signature {
|
||||||
|
use crate::dsa::rpo_falcon512::{
|
||||||
|
hash_to_point::hash_to_point_shake256, tests::ChaCha, CHACHA_SEED_LEN,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut dummy = vec![0_u8; skip_bytes];
|
||||||
|
rng.fill_bytes(&mut dummy);
|
||||||
|
let mut nonce_bytes = [0u8; SIG_NONCE_LEN];
|
||||||
|
rng.fill_bytes(&mut nonce_bytes);
|
||||||
|
let nonce = Nonce::new(nonce_bytes);
|
||||||
|
|
||||||
|
let h = self.compute_pub_key_poly();
|
||||||
|
let c = hash_to_point_shake256(message, &nonce);
|
||||||
|
|
||||||
|
let s2 = loop {
|
||||||
|
let mut chacha_seed = [0_u8; CHACHA_SEED_LEN];
|
||||||
|
rng.fill_bytes(&mut chacha_seed);
|
||||||
|
|
||||||
|
let mut chacha_prng = ChaCha::new(chacha_seed.to_vec());
|
||||||
|
|
||||||
|
let s2 = self.sign_helper_testing(c.clone(), &mut chacha_prng);
|
||||||
|
if let Some(s2) = s2 {
|
||||||
|
break s2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Signature::new(nonce, h, s2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signs a message polynomial with the secret key.
|
||||||
|
///
|
||||||
|
/// Takes a randomness generator implementing `Rng` and message polynomial representing `c`
|
||||||
|
/// the hash-to-point of the message to be signed. It outputs a signature polynomial `s2`.
|
||||||
|
#[cfg(test)]
|
||||||
|
fn sign_helper_testing<R: Rng>(
|
||||||
|
&self,
|
||||||
|
c: Polynomial<FalconFelt>,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> Option<SignaturePoly> {
|
||||||
|
let one_over_q = 1.0 / (MODULUS as f64);
|
||||||
|
let c_over_q_fft = c.map(|cc| Complex::new(one_over_q * cc.value() as f64, 0.0)).fft();
|
||||||
|
|
||||||
|
// B = [[FFT(g), -FFT(f)], [FFT(G), -FFT(F)]]
|
||||||
|
let [g_fft, minus_f_fft, big_g_fft, minus_big_f_fft] = to_complex_fft(&self.secret_key);
|
||||||
|
let t0 = c_over_q_fft.hadamard_mul(&minus_big_f_fft);
|
||||||
|
let t1 = -c_over_q_fft.hadamard_mul(&minus_f_fft);
|
||||||
|
|
||||||
|
let z = ffsampling(&(t0.clone(), t1.clone()), &self.tree, rng);
|
||||||
|
let t0_min_z0 = t0.clone() - z.0;
|
||||||
|
let t1_min_z1 = t1.clone() - z.1;
|
||||||
|
|
||||||
|
// s = (t-z) * B
|
||||||
|
let s0 = t0_min_z0.hadamard_mul(&g_fft) + t1_min_z1.hadamard_mul(&big_g_fft);
|
||||||
|
let s1 = t0_min_z0.hadamard_mul(&minus_f_fft) + t1_min_z1.hadamard_mul(&minus_big_f_fft);
|
||||||
|
|
||||||
|
// compute the norm of (s0||s1) and note that they are in FFT representation
|
||||||
|
let length_squared: f64 = (s0.coefficients.iter().map(|a| (a * a.conj()).re).sum::<f64>()
|
||||||
|
+ s1.coefficients.iter().map(|a| (a * a.conj()).re).sum::<f64>())
|
||||||
|
/ (N as f64);
|
||||||
|
|
||||||
|
if length_squared < (SIG_L2_BOUND as f64) {
|
||||||
|
let bold_s = [-s0, s1];
|
||||||
|
|
||||||
|
let s2 = bold_s[1].ifft();
|
||||||
|
let s2_coef: [i16; N] = s2
|
||||||
|
.coefficients
|
||||||
|
.iter()
|
||||||
|
.map(|a| a.re.round() as i16)
|
||||||
|
.collect::<Vec<i16>>()
|
||||||
|
.try_into()
|
||||||
|
.expect("The number of coefficients should be equal to N");
|
||||||
|
|
||||||
|
if let Ok(s2) = SignaturePoly::try_from(&s2_coef) {
|
||||||
|
Some(s2)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SERIALIZATION / DESERIALIZATION
|
// SERIALIZATION / DESERIALIZATION
|
||||||
@@ -304,9 +396,9 @@ impl Deserializable for SecretKey {
|
|||||||
fn to_complex_fft(basis: &[Polynomial<i16>; 4]) -> [Polynomial<Complex<f64>>; 4] {
|
fn to_complex_fft(basis: &[Polynomial<i16>; 4]) -> [Polynomial<Complex<f64>>; 4] {
|
||||||
let [g, f, big_g, big_f] = basis.clone();
|
let [g, f, big_g, big_f] = basis.clone();
|
||||||
let g_fft = g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
let g_fft = g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
||||||
let minus_f_fft = f.map(|cc| -Complex64::new(*cc as f64, 0.0)).fft();
|
let minus_f_fft = f.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
||||||
let big_g_fft = big_g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
let big_g_fft = big_g.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
||||||
let minus_big_f_fft = big_f.map(|cc| -Complex64::new(*cc as f64, 0.0)).fft();
|
let minus_big_f_fft = big_f.map(|cc| Complex64::new(*cc as f64, 0.0)).fft();
|
||||||
[g_fft, minus_f_fft, big_g_fft, minus_big_f_fft]
|
[g_fft, minus_f_fft, big_g_fft, minus_big_f_fft]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use core::f64::consts::LN_2;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
@@ -28,7 +27,11 @@ fn base_sampler(bytes: [u8; 9]) -> i16 {
|
|||||||
198,
|
198,
|
||||||
1,
|
1,
|
||||||
];
|
];
|
||||||
let u = u128::from_be_bytes([vec![0u8; 7], bytes.to_vec()].concat().try_into().unwrap());
|
|
||||||
|
let mut tmp = bytes.to_vec();
|
||||||
|
tmp.extend_from_slice(&[0u8; 7]);
|
||||||
|
tmp.reverse();
|
||||||
|
let u = u128::from_be_bytes(tmp.try_into().expect("should have length 16"));
|
||||||
RCDT.into_iter().filter(|r| u < *r).count() as i16
|
RCDT.into_iter().filter(|r| u < *r).count() as i16
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,16 +75,20 @@ fn approx_exp(x: f64, ccs: f64) -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A random bool that is true with probability ≈ ccs · exp(-x).
|
/// A random bool that is true with probability ≈ ccs · exp(-x).
|
||||||
fn ber_exp(x: f64, ccs: f64, random_bytes: [u8; 7]) -> bool {
|
fn ber_exp<R: Rng>(x: f64, ccs: f64, rng: &mut R) -> bool {
|
||||||
// 0.69314718055994530941 = ln(2)
|
const LN2: f64 = core::f64::consts::LN_2;
|
||||||
let s = f64::floor(x / LN_2) as usize;
|
const ILN2: f64 = 1.0 / LN2;
|
||||||
let r = x - LN_2 * (s as f64);
|
let s = f64::floor(x * ILN2);
|
||||||
let shamt = usize::min(s, 63);
|
let r = x - s * LN2;
|
||||||
let z = ((((approx_exp(r, ccs) as u128) << 1) - 1) >> shamt) as u64;
|
let s = (s as u64).min(63);
|
||||||
let mut w = 0i16;
|
let z = ((approx_exp(r, ccs) << 1) - 1) >> s;
|
||||||
for (index, i) in (0..64).step_by(8).rev().enumerate() {
|
|
||||||
let byte = random_bytes[index];
|
let mut w = 0_i32;
|
||||||
w = (byte as i16) - (((z >> i) & 0xff) as i16);
|
for i in (0..=56).rev().step_by(8) {
|
||||||
|
let mut dest = [0_u8; 1];
|
||||||
|
rng.fill_bytes(&mut dest);
|
||||||
|
let p = u8::from_be_bytes(dest);
|
||||||
|
w = (p as i32) - (z >> i & 0xFF) as i32;
|
||||||
if w != 0 {
|
if w != 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -100,14 +107,20 @@ pub(crate) fn sampler_z<R: Rng>(mu: f64, sigma: f64, sigma_min: f64, rng: &mut R
|
|||||||
let r = mu - s;
|
let r = mu - s;
|
||||||
let ccs = sigma_min * isigma;
|
let ccs = sigma_min * isigma;
|
||||||
loop {
|
loop {
|
||||||
let z0 = base_sampler(rng.gen());
|
let mut dest = [0_u8; 9];
|
||||||
let random_byte: u8 = rng.gen();
|
rng.fill_bytes(&mut dest);
|
||||||
|
let z0 = base_sampler(dest);
|
||||||
|
|
||||||
|
let mut dest = [0_u8; 1];
|
||||||
|
rng.fill_bytes(&mut dest);
|
||||||
|
let random_byte: u8 = dest[0];
|
||||||
|
|
||||||
let b = (random_byte & 1) as i16;
|
let b = (random_byte & 1) as i16;
|
||||||
let z = b + ((b << 1) - 1) * z0;
|
let z = b + (2 * b - 1) * z0;
|
||||||
let zf_min_r = (z as f64) - r;
|
let zf_min_r = (z as f64) - r;
|
||||||
// x = ((z-r)^2)/(2*sigma^2) - ((z-b)^2)/(2*sigma0^2)
|
|
||||||
let x = zf_min_r * zf_min_r * dss - (z0 * z0) as f64 * INV_2SIGMA_MAX_SQ;
|
let x = zf_min_r * zf_min_r * dss - (z0 * z0) as f64 * INV_2SIGMA_MAX_SQ;
|
||||||
if ber_exp(x, ccs, rng.gen()) {
|
|
||||||
|
if ber_exp(x, ccs, rng) {
|
||||||
return z + (s as i16);
|
return z + (s as i16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,80 +128,7 @@ pub(crate) fn sampler_z<R: Rng>(mu: f64, sigma: f64, sigma_min: f64, rng: &mut R
|
|||||||
|
|
||||||
#[cfg(all(test, feature = "std"))]
|
#[cfg(all(test, feature = "std"))]
|
||||||
mod test {
|
mod test {
|
||||||
use alloc::vec::Vec;
|
use super::approx_exp;
|
||||||
use rand::RngCore;
|
|
||||||
use std::{thread::sleep, time::Duration};
|
|
||||||
|
|
||||||
use super::{approx_exp, ber_exp, sampler_z};
|
|
||||||
|
|
||||||
/// RNG used only for testing purposes, whereby the produced
|
|
||||||
/// string of random bytes is equal to the one it is initialized
|
|
||||||
/// with. Whatever you do, do not use this RNG in production.
|
|
||||||
struct UnsafeBufferRng {
|
|
||||||
buffer: Vec<u8>,
|
|
||||||
index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnsafeBufferRng {
|
|
||||||
fn new(buffer: &[u8]) -> Self {
|
|
||||||
Self { buffer: buffer.to_vec(), index: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next(&mut self) -> u8 {
|
|
||||||
if self.buffer.len() <= self.index {
|
|
||||||
// panic!("Ran out of buffer.");
|
|
||||||
sleep(Duration::from_millis(10));
|
|
||||||
0u8
|
|
||||||
} else {
|
|
||||||
let return_value = self.buffer[self.index];
|
|
||||||
self.index += 1;
|
|
||||||
return_value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RngCore for UnsafeBufferRng {
|
|
||||||
fn next_u32(&mut self) -> u32 {
|
|
||||||
// let bytes: [u8; 4] = (0..4)
|
|
||||||
// .map(|_| self.next())
|
|
||||||
// .collect_vec()
|
|
||||||
// .try_into()
|
|
||||||
// .unwrap();
|
|
||||||
// u32::from_be_bytes(bytes)
|
|
||||||
u32::from_le_bytes([self.next(), 0, 0, 0])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_u64(&mut self) -> u64 {
|
|
||||||
// let bytes: [u8; 8] = (0..8)
|
|
||||||
// .map(|_| self.next())
|
|
||||||
// .collect_vec()
|
|
||||||
// .try_into()
|
|
||||||
// .unwrap();
|
|
||||||
// u64::from_be_bytes(bytes)
|
|
||||||
u64::from_le_bytes([self.next(), 0, 0, 0, 0, 0, 0, 0])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
|
||||||
for d in dest.iter_mut() {
|
|
||||||
*d = self.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
|
|
||||||
for d in dest.iter_mut() {
|
|
||||||
*d = self.next();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unsafe_buffer_rng() {
|
|
||||||
let seed_bytes = hex::decode("7FFECD162AE2").unwrap();
|
|
||||||
let mut rng = UnsafeBufferRng::new(&seed_bytes);
|
|
||||||
let generated_bytes: Vec<u8> = (0..seed_bytes.len()).map(|_| rng.next()).collect();
|
|
||||||
assert_eq!(seed_bytes, generated_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_approx_exp() {
|
fn test_approx_exp() {
|
||||||
@@ -230,69 +170,4 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ber_exp() {
|
|
||||||
let kats = [
|
|
||||||
(
|
|
||||||
1.268_314_048_020_498_4,
|
|
||||||
0.749_990_853_267_664_9,
|
|
||||||
hex::decode("ea000000000000").unwrap(),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
0.001_563_917_959_143_409_6,
|
|
||||||
0.749_990_853_267_664_9,
|
|
||||||
hex::decode("6c000000000000").unwrap(),
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
0.017_921_215_753_999_235,
|
|
||||||
0.749_990_853_267_664_9,
|
|
||||||
hex::decode("c2000000000000").unwrap(),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
0.776_117_648_844_980_6,
|
|
||||||
0.751_181_554_542_520_8,
|
|
||||||
hex::decode("58000000000000").unwrap(),
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
for (x, ccs, bytes, answer) in kats {
|
|
||||||
assert_eq!(answer, ber_exp(x, ccs, bytes.try_into().unwrap()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sampler_z() {
|
|
||||||
let sigma_min = 1.277833697;
|
|
||||||
// known answers from the doc, table 3.2, page 44
|
|
||||||
// https://falcon-sign.info/falcon.pdf
|
|
||||||
// The zeros were added to account for dropped bytes.
|
|
||||||
let kats = [
|
|
||||||
(-91.90471153063714,1.7037990414754918,hex::decode("0fc5442ff043d66e91d1ea000000000000cac64ea5450a22941edc6c").unwrap(),-92),
|
|
||||||
(-8.322564895434937,1.7037990414754918,hex::decode("f4da0f8d8444d1a77265c2000000000000ef6f98bbbb4bee7db8d9b3").unwrap(),-8),
|
|
||||||
(-19.096516109216804,1.7035823083824078,hex::decode("db47f6d7fb9b19f25c36d6000000000000b9334d477a8bc0be68145d").unwrap(),-20),
|
|
||||||
(-11.335543982423326, 1.7035823083824078, hex::decode("ae41b4f5209665c74d00dc000000000000c1a8168a7bb516b3190cb42c1ded26cd52000000000000aed770eca7dd334e0547bcc3c163ce0b").unwrap(), -12),
|
|
||||||
(7.9386734193997555, 1.6984647769450156, hex::decode("31054166c1012780c603ae0000000000009b833cec73f2f41ca5807c000000000000c89c92158834632f9b1555").unwrap(), 8),
|
|
||||||
(-28.990850086867255, 1.6984647769450156, hex::decode("737e9d68a50a06dbbc6477").unwrap(), -30),
|
|
||||||
(-9.071257914091655, 1.6980782114808988, hex::decode("a98ddd14bf0bf22061d632").unwrap(), -10),
|
|
||||||
(-43.88754568839566, 1.6980782114808988, hex::decode("3cbf6818a68f7ab9991514").unwrap(), -41),
|
|
||||||
(-58.17435547946095,1.7010983419195522,hex::decode("6f8633f5bfa5d26848668e0000000000003d5ddd46958e97630410587c").unwrap(),-61),
|
|
||||||
(-43.58664906684732, 1.7010983419195522, hex::decode("272bc6c25f5c5ee53f83c40000000000003a361fbc7cc91dc783e20a").unwrap(), -46),
|
|
||||||
(-34.70565203313315, 1.7009387219711465, hex::decode("45443c59574c2c3b07e2e1000000000000d9071e6d133dbe32754b0a").unwrap(), -34),
|
|
||||||
(-44.36009577368896, 1.7009387219711465, hex::decode("6ac116ed60c258e2cbaeab000000000000728c4823e6da36e18d08da0000000000005d0cc104e21cc7fd1f5ca8000000000000d9dbb675266c928448059e").unwrap(), -44),
|
|
||||||
(-21.783037079346236, 1.6958406126012802, hex::decode("68163bc1e2cbf3e18e7426").unwrap(), -23),
|
|
||||||
(-39.68827784633828, 1.6958406126012802, hex::decode("d6a1b51d76222a705a0259").unwrap(), -40),
|
|
||||||
(-18.488607061056847, 1.6955259305261838, hex::decode("f0523bfaa8a394bf4ea5c10000000000000f842366fde286d6a30803").unwrap(), -22),
|
|
||||||
(-48.39610939101591, 1.6955259305261838, hex::decode("87bd87e63374cee62127fc0000000000006931104aab64f136a0485b").unwrap(), -50),
|
|
||||||
];
|
|
||||||
for (mu, sigma, random_bytes, answer) in kats {
|
|
||||||
assert_eq!(
|
|
||||||
sampler_z(mu, sigma, sigma_min, &mut UnsafeBufferRng::new(&random_bytes)),
|
|
||||||
answer
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ mod keys;
|
|||||||
mod math;
|
mod math;
|
||||||
mod signature;
|
mod signature;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
pub use self::keys::{PubKeyPoly, PublicKey, SecretKey};
|
pub use self::keys::{PubKeyPoly, PublicKey, SecretKey};
|
||||||
pub use self::math::Polynomial;
|
pub use self::math::Polynomial;
|
||||||
pub use self::signature::{Signature, SignatureHeader, SignaturePoly};
|
pub use self::signature::{Signature, SignatureHeader, SignaturePoly};
|
||||||
@@ -48,6 +51,10 @@ const SIG_L2_BOUND: u64 = 34034726;
|
|||||||
/// Standard deviation of the Gaussian over the lattice.
|
/// Standard deviation of the Gaussian over the lattice.
|
||||||
const SIGMA: f64 = 165.7366171829776;
|
const SIGMA: f64 = 165.7366171829776;
|
||||||
|
|
||||||
|
/// Length of the seed for the ChaCha20-based PRNG.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) const CHACHA_SEED_LEN: usize = 56;
|
||||||
|
|
||||||
// TYPE ALIASES
|
// TYPE ALIASES
|
||||||
// ================================================================================================
|
// ================================================================================================
|
||||||
|
|
||||||
|
|||||||
2030
src/dsa/rpo_falcon512/tests.rs
Normal file
2030
src/dsa/rpo_falcon512/tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,9 @@ pub use winter_utils::Randomizable;
|
|||||||
use crate::{Felt, FieldElement, Word, ZERO};
|
use crate::{Felt, FieldElement, Word, ZERO};
|
||||||
|
|
||||||
mod rpo;
|
mod rpo;
|
||||||
|
mod rpx;
|
||||||
pub use rpo::RpoRandomCoin;
|
pub use rpo::RpoRandomCoin;
|
||||||
|
pub use rpx::RpxRandomCoin;
|
||||||
|
|
||||||
/// Pseudo-random element generator.
|
/// Pseudo-random element generator.
|
||||||
///
|
///
|
||||||
|
|||||||
292
src/rand/rpx.rs
Normal file
292
src/rand/rpx.rs
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
use super::{Felt, FeltRng, FieldElement, RandomCoin, RandomCoinError, RngCore, Word, ZERO};
|
||||||
|
use crate::{
|
||||||
|
hash::rpx::{Rpx256, RpxDigest},
|
||||||
|
utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
|
||||||
|
};
|
||||||
|
use alloc::{string::ToString, vec::Vec};
|
||||||
|
use rand_core::impls;
|
||||||
|
|
||||||
|
// CONSTANTS
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
const STATE_WIDTH: usize = Rpx256::STATE_WIDTH;
|
||||||
|
const RATE_START: usize = Rpx256::RATE_RANGE.start;
|
||||||
|
const RATE_END: usize = Rpx256::RATE_RANGE.end;
|
||||||
|
const HALF_RATE_WIDTH: usize = (Rpx256::RATE_RANGE.end - Rpx256::RATE_RANGE.start) / 2;
|
||||||
|
|
||||||
|
// RPX RANDOM COIN
|
||||||
|
// ================================================================================================
|
||||||
|
/// A simplified version of the `SPONGE_PRG` reseedable pseudo-random number generator algorithm
|
||||||
|
/// described in <https://eprint.iacr.org/2011/499.pdf>.
|
||||||
|
///
|
||||||
|
/// The simplification is related to the following facts:
|
||||||
|
/// 1. A call to the reseed method implies one and only one call to the permutation function.
|
||||||
|
/// This is possible because in our case we never reseed with more than 4 field elements.
|
||||||
|
/// 2. As a result of the previous point, we don't make use of an input buffer to accumulate seed
|
||||||
|
/// material.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct RpxRandomCoin {
|
||||||
|
state: [Felt; STATE_WIDTH],
|
||||||
|
current: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpxRandomCoin {
|
||||||
|
/// Returns a new [RpxRandomCoin] initialize with the specified seed.
|
||||||
|
pub fn new(seed: Word) -> Self {
|
||||||
|
let mut state = [ZERO; STATE_WIDTH];
|
||||||
|
|
||||||
|
for i in 0..HALF_RATE_WIDTH {
|
||||||
|
state[RATE_START + i] += seed[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absorb
|
||||||
|
Rpx256::apply_permutation(&mut state);
|
||||||
|
|
||||||
|
RpxRandomCoin { state, current: RATE_START }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an [RpxRandomCoin] instantiated from the provided components.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if `current` is smaller than 4 or greater than or equal to 12.
|
||||||
|
pub fn from_parts(state: [Felt; STATE_WIDTH], current: usize) -> Self {
|
||||||
|
assert!(
|
||||||
|
(RATE_START..RATE_END).contains(¤t),
|
||||||
|
"current value outside of valid range"
|
||||||
|
);
|
||||||
|
Self { state, current }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns components of this random coin.
|
||||||
|
pub fn into_parts(self) -> ([Felt; STATE_WIDTH], usize) {
|
||||||
|
(self.state, self.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills `dest` with random data.
|
||||||
|
pub fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||||
|
<Self as RngCore>::fill_bytes(self, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_basefield(&mut self) -> Felt {
|
||||||
|
if self.current == RATE_END {
|
||||||
|
Rpx256::apply_permutation(&mut self.state);
|
||||||
|
self.current = RATE_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current += 1;
|
||||||
|
self.state[self.current - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RANDOM COIN IMPLEMENTATION
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
impl RandomCoin for RpxRandomCoin {
|
||||||
|
type BaseField = Felt;
|
||||||
|
type Hasher = Rpx256;
|
||||||
|
|
||||||
|
fn new(seed: &[Self::BaseField]) -> Self {
|
||||||
|
let digest: Word = Rpx256::hash_elements(seed).into();
|
||||||
|
Self::new(digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reseed(&mut self, data: RpxDigest) {
|
||||||
|
// Reset buffer
|
||||||
|
self.current = RATE_START;
|
||||||
|
|
||||||
|
// Add the new seed material to the first half of the rate portion of the RPX state
|
||||||
|
let data: Word = data.into();
|
||||||
|
|
||||||
|
self.state[RATE_START] += data[0];
|
||||||
|
self.state[RATE_START + 1] += data[1];
|
||||||
|
self.state[RATE_START + 2] += data[2];
|
||||||
|
self.state[RATE_START + 3] += data[3];
|
||||||
|
|
||||||
|
// Absorb
|
||||||
|
Rpx256::apply_permutation(&mut self.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_leading_zeros(&self, value: u64) -> u32 {
|
||||||
|
let value = Felt::new(value);
|
||||||
|
let mut state_tmp = self.state;
|
||||||
|
|
||||||
|
state_tmp[RATE_START] += value;
|
||||||
|
|
||||||
|
Rpx256::apply_permutation(&mut state_tmp);
|
||||||
|
|
||||||
|
let first_rate_element = state_tmp[RATE_START].as_int();
|
||||||
|
first_rate_element.trailing_zeros()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw<E: FieldElement<BaseField = Felt>>(&mut self) -> Result<E, RandomCoinError> {
|
||||||
|
let ext_degree = E::EXTENSION_DEGREE;
|
||||||
|
let mut result = vec![ZERO; ext_degree];
|
||||||
|
for r in result.iter_mut().take(ext_degree) {
|
||||||
|
*r = self.draw_basefield();
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = E::slice_from_base_elements(&result);
|
||||||
|
Ok(result[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_integers(
|
||||||
|
&mut self,
|
||||||
|
num_values: usize,
|
||||||
|
domain_size: usize,
|
||||||
|
nonce: u64,
|
||||||
|
) -> Result<Vec<usize>, RandomCoinError> {
|
||||||
|
assert!(domain_size.is_power_of_two(), "domain size must be a power of two");
|
||||||
|
assert!(num_values < domain_size, "number of values must be smaller than domain size");
|
||||||
|
|
||||||
|
// absorb the nonce
|
||||||
|
let nonce = Felt::new(nonce);
|
||||||
|
self.state[RATE_START] += nonce;
|
||||||
|
Rpx256::apply_permutation(&mut self.state);
|
||||||
|
|
||||||
|
// reset the buffer
|
||||||
|
self.current = RATE_START;
|
||||||
|
|
||||||
|
// determine how many bits are needed to represent valid values in the domain
|
||||||
|
let v_mask = (domain_size - 1) as u64;
|
||||||
|
|
||||||
|
// draw values from PRNG until we get as many unique values as specified by num_queries
|
||||||
|
let mut values = Vec::new();
|
||||||
|
for _ in 0..1000 {
|
||||||
|
// get the next pseudo-random field element
|
||||||
|
let value = self.draw_basefield().as_int();
|
||||||
|
|
||||||
|
// use the mask to get a value within the range
|
||||||
|
let value = (value & v_mask) as usize;
|
||||||
|
|
||||||
|
values.push(value);
|
||||||
|
if values.len() == num_values {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if values.len() < num_values {
|
||||||
|
return Err(RandomCoinError::FailedToDrawIntegers(num_values, values.len(), 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FELT RNG IMPLEMENTATION
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
impl FeltRng for RpxRandomCoin {
|
||||||
|
fn draw_element(&mut self) -> Felt {
|
||||||
|
self.draw_basefield()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_word(&mut self) -> Word {
|
||||||
|
let mut output = [ZERO; 4];
|
||||||
|
for o in output.iter_mut() {
|
||||||
|
*o = self.draw_basefield();
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RNGCORE IMPLEMENTATION
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
impl RngCore for RpxRandomCoin {
|
||||||
|
fn next_u32(&mut self) -> u32 {
|
||||||
|
self.draw_basefield().as_int() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_u64(&mut self) -> u64 {
|
||||||
|
impls::next_u64_via_u32(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||||
|
impls::fill_bytes_via_next(self, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
|
||||||
|
self.fill_bytes(dest);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SERIALIZATION
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
impl Serializable for RpxRandomCoin {
|
||||||
|
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||||
|
self.state.iter().for_each(|v| v.write_into(target));
|
||||||
|
// casting to u8 is OK because `current` is always between 4 and 12.
|
||||||
|
target.write_u8(self.current as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deserializable for RpxRandomCoin {
|
||||||
|
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||||
|
let state = [
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
Felt::read_from(source)?,
|
||||||
|
];
|
||||||
|
let current = source.read_u8()? as usize;
|
||||||
|
if !(RATE_START..RATE_END).contains(¤t) {
|
||||||
|
return Err(DeserializationError::InvalidValue(
|
||||||
|
"current value outside of valid range".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(Self { state, current })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TESTS
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{Deserializable, FeltRng, RpxRandomCoin, Serializable, ZERO};
|
||||||
|
use crate::ONE;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_feltrng_felt() {
|
||||||
|
let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]);
|
||||||
|
let output = rpxcoin.draw_element();
|
||||||
|
|
||||||
|
let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]);
|
||||||
|
let expected = rpxcoin.draw_basefield();
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_feltrng_word() {
|
||||||
|
let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]);
|
||||||
|
let output = rpxcoin.draw_word();
|
||||||
|
|
||||||
|
let mut rpocoin = RpxRandomCoin::new([ZERO; 4]);
|
||||||
|
let mut expected = [ZERO; 4];
|
||||||
|
for o in expected.iter_mut() {
|
||||||
|
*o = rpocoin.draw_basefield();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_feltrng_serialization() {
|
||||||
|
let coin1 = RpxRandomCoin::from_parts([ONE; 12], 5);
|
||||||
|
|
||||||
|
let bytes = coin1.to_bytes();
|
||||||
|
let coin2 = RpxRandomCoin::read_from_bytes(&bytes).unwrap();
|
||||||
|
assert_eq!(coin1, coin2);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user