feat: update error types for sdk
This commit is contained in:
parent
1cbcda71cb
commit
0944abc0e2
|
@ -1,7 +1,11 @@
|
|||
//! Errors related to proving and verifying proofs.
|
||||
use thiserror::Error;
|
||||
use crate::range_proof::errors::RangeProofError;
|
||||
use crate::{
|
||||
range_proof::errors::RangeProofError,
|
||||
sigma_proofs::errors::*,
|
||||
};
|
||||
|
||||
// TODO: clean up errors for encryption
|
||||
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ProofError {
|
||||
#[error("proof failed to verify")]
|
||||
|
@ -23,7 +27,30 @@ pub enum TranscriptError {
|
|||
}
|
||||
|
||||
impl From<RangeProofError> for ProofError {
|
||||
fn from(err: RangeProofError) -> Self {
|
||||
fn from(_err: RangeProofError) -> Self {
|
||||
Self::RangeProofError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EqualityProofError> for ProofError {
|
||||
fn from(_err: EqualityProofError) -> Self {
|
||||
Self::SigmaProofError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FeeProofError> for ProofError {
|
||||
fn from(_err: FeeProofError) -> Self {
|
||||
Self::SigmaProofError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ZeroBalanceProofError> for ProofError {
|
||||
fn from(_err: ZeroBalanceProofError) -> Self {
|
||||
Self::SigmaProofError
|
||||
}
|
||||
}
|
||||
impl From<ValidityProofError> for ProofError {
|
||||
fn from(_err: ValidityProofError) -> Self {
|
||||
Self::SigmaProofError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,8 @@ impl CloseAccountProof {
|
|||
|
||||
// verify zero balance proof
|
||||
let proof: ZeroBalanceProof = self.proof.try_into()?;
|
||||
proof.verify(elgamal_pubkey, balance, &mut transcript)
|
||||
proof.verify(elgamal_pubkey, balance, &mut transcript)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ pub enum RangeProofError {
|
|||
}
|
||||
|
||||
impl From<TranscriptError> for RangeProofError {
|
||||
fn from(err: TranscriptError) -> Self {
|
||||
fn from(_err: TranscriptError) -> Self {
|
||||
Self::TranscriptError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use {
|
|||
rand::rngs::OsRng,
|
||||
};
|
||||
use {
|
||||
crate::{errors::ProofError, transcript::TranscriptProtocol},
|
||||
crate::{sigma_proofs::errors::EqualityProofError, transcript::TranscriptProtocol},
|
||||
arrayref::{array_ref, array_refs},
|
||||
curve25519_dalek::{
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
|
@ -88,7 +88,7 @@ impl EqualityProof {
|
|||
ciphertext: &ElGamalCiphertext,
|
||||
commitment: &PedersenCommitment,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ProofError> {
|
||||
) -> Result<(), EqualityProofError> {
|
||||
// extract the relevant scalar and Ristretto points from the inputs
|
||||
let G = PedersenBase::default().G;
|
||||
let H = PedersenBase::default().H;
|
||||
|
@ -109,9 +109,9 @@ impl EqualityProof {
|
|||
let ww = w * w;
|
||||
|
||||
// check that the required algebraic condition holds
|
||||
let Y_0 = self.Y_0.decompress().ok_or(ProofError::VerificationError)?;
|
||||
let Y_1 = self.Y_1.decompress().ok_or(ProofError::VerificationError)?;
|
||||
let Y_2 = self.Y_2.decompress().ok_or(ProofError::VerificationError)?;
|
||||
let Y_0 = self.Y_0.decompress().ok_or(EqualityProofError::FormatError)?;
|
||||
let Y_1 = self.Y_1.decompress().ok_or(EqualityProofError::FormatError)?;
|
||||
let Y_2 = self.Y_2.decompress().ok_or(EqualityProofError::FormatError)?;
|
||||
|
||||
let check = RistrettoPoint::vartime_multiscalar_mul(
|
||||
vec![
|
||||
|
@ -133,7 +133,7 @@ impl EqualityProof {
|
|||
if check.is_identity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProofError::VerificationError)
|
||||
Err(EqualityProofError::AlgebraicRelationError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ impl EqualityProof {
|
|||
buf
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofError> {
|
||||
let bytes = array_ref![bytes, 0, 192];
|
||||
let (Y_0, Y_1, Y_2, z_s, z_x, z_r) = array_refs![bytes, 32, 32, 32, 32, 32, 32];
|
||||
|
||||
|
@ -156,9 +156,9 @@ impl EqualityProof {
|
|||
let Y_1 = CompressedRistretto::from_slice(Y_1);
|
||||
let Y_2 = CompressedRistretto::from_slice(Y_2);
|
||||
|
||||
let z_s = Scalar::from_canonical_bytes(*z_s).ok_or(ProofError::FormatError)?;
|
||||
let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(ProofError::FormatError)?;
|
||||
let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(ProofError::FormatError)?;
|
||||
let z_s = Scalar::from_canonical_bytes(*z_s).ok_or(EqualityProofError::FormatError)?;
|
||||
let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(EqualityProofError::FormatError)?;
|
||||
let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(EqualityProofError::FormatError)?;
|
||||
|
||||
Ok(EqualityProof {
|
||||
Y_0,
|
||||
|
|
|
@ -3,7 +3,7 @@ use thiserror::Error;
|
|||
use crate::errors::TranscriptError;
|
||||
|
||||
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum EqualityProof {
|
||||
pub enum EqualityProofError {
|
||||
#[error("the required algebraic relation does not hold")]
|
||||
AlgebraicRelationError,
|
||||
#[error("malformed proof")]
|
||||
|
@ -14,14 +14,14 @@ pub enum EqualityProof {
|
|||
TranscriptError,
|
||||
}
|
||||
|
||||
impl From<TranscriptError> for EqualityProof {
|
||||
fn from(err: TranscriptError) -> Self {
|
||||
impl From<TranscriptError> for EqualityProofError {
|
||||
fn from(_err: TranscriptError) -> Self {
|
||||
Self::TranscriptError
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ValidityProof {
|
||||
pub enum ValidityProofError {
|
||||
#[error("the required algebraic relation does not hold")]
|
||||
AlgebraicRelationError,
|
||||
#[error("malformed proof")]
|
||||
|
@ -32,14 +32,14 @@ pub enum ValidityProof {
|
|||
TranscriptError,
|
||||
}
|
||||
|
||||
impl From<TranscriptError> for ValidityProof {
|
||||
fn from(err: TranscriptError) -> Self {
|
||||
impl From<TranscriptError> for ValidityProofError {
|
||||
fn from(_err: TranscriptError) -> Self {
|
||||
Self::TranscriptError
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ZeroBalanceProof {
|
||||
pub enum ZeroBalanceProofError {
|
||||
#[error("the required algebraic relation does not hold")]
|
||||
AlgebraicRelationError,
|
||||
#[error("malformed proof")]
|
||||
|
@ -50,8 +50,26 @@ pub enum ZeroBalanceProof {
|
|||
TranscriptError,
|
||||
}
|
||||
|
||||
impl From<TranscriptError> for ZeroBalanceProof {
|
||||
fn from(err: TranscriptError) -> Self {
|
||||
impl From<TranscriptError> for ZeroBalanceProofError {
|
||||
fn from(_err: TranscriptError) -> Self {
|
||||
Self::TranscriptError
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum FeeProofError {
|
||||
#[error("the required algebraic relation does not hold")]
|
||||
AlgebraicRelationError,
|
||||
#[error("malformed proof")]
|
||||
FormatError,
|
||||
#[error("multiscalar multiplication failed")]
|
||||
MultiscalarMulError,
|
||||
#[error("transcript failed to produce a challenge")]
|
||||
TranscriptError,
|
||||
}
|
||||
|
||||
impl From<TranscriptError> for FeeProofError {
|
||||
fn from(_err: TranscriptError) -> Self {
|
||||
Self::TranscriptError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use {
|
|||
rand::rngs::OsRng,
|
||||
};
|
||||
use {
|
||||
crate::{errors::ProofError, transcript::TranscriptProtocol},
|
||||
crate::{sigma_proofs::errors::FeeProofError, transcript::TranscriptProtocol},
|
||||
curve25519_dalek::{
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
scalar::Scalar,
|
||||
|
@ -172,7 +172,7 @@ impl FeeProof {
|
|||
commitment_delta_real: PedersenCommitment,
|
||||
commitment_delta_claimed: PedersenCommitment,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ProofError> {
|
||||
) -> Result<(), FeeProofError> {
|
||||
// extract the relevant scalar and Ristretto points from the input
|
||||
let G = PedersenBase::default().G;
|
||||
let H = PedersenBase::default().H;
|
||||
|
@ -202,19 +202,19 @@ impl FeeProof {
|
|||
.fee_max_proof
|
||||
.Y_max
|
||||
.decompress()
|
||||
.ok_or(ProofError::VerificationError)?;
|
||||
.ok_or(FeeProofError::FormatError)?;
|
||||
let z_max = self.fee_max_proof.z_max;
|
||||
|
||||
let Y_delta_real = self
|
||||
.fee_equality_proof
|
||||
.Y_delta_real
|
||||
.decompress()
|
||||
.ok_or(ProofError::VerificationError)?;
|
||||
.ok_or(FeeProofError::FormatError)?;
|
||||
let Y_delta_claimed = self
|
||||
.fee_equality_proof
|
||||
.Y_delta_claimed
|
||||
.decompress()
|
||||
.ok_or(ProofError::VerificationError)?;
|
||||
.ok_or(FeeProofError::FormatError)?;
|
||||
let z_x = self.fee_equality_proof.z_x;
|
||||
let z_delta_real = self.fee_equality_proof.z_delta_real;
|
||||
let z_delta_claimed = self.fee_equality_proof.z_delta_claimed;
|
||||
|
@ -262,7 +262,7 @@ impl FeeProof {
|
|||
if check.is_identity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProofError::VerificationError)
|
||||
Err(FeeProofError::AlgebraicRelationError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use {
|
|||
rand::rngs::OsRng,
|
||||
};
|
||||
use {
|
||||
crate::{errors::ProofError, transcript::TranscriptProtocol},
|
||||
crate::{sigma_proofs::errors::ValidityProofError, transcript::TranscriptProtocol},
|
||||
arrayref::{array_ref, array_refs},
|
||||
curve25519_dalek::{
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
|
@ -87,7 +87,7 @@ impl ValidityProof {
|
|||
handle_dest: (&PedersenDecryptHandle, &PedersenDecryptHandle),
|
||||
handle_auditor: (&PedersenDecryptHandle, &PedersenDecryptHandle),
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ProofError> {
|
||||
) -> Result<(), ValidityProofError> {
|
||||
// extract the relevant scalar and Ristretto points from the inputs
|
||||
let G = PedersenBase::default().G;
|
||||
let H = PedersenBase::default().H;
|
||||
|
@ -103,9 +103,9 @@ impl ValidityProof {
|
|||
let ww = w * w;
|
||||
|
||||
// check the required algebraic conditions
|
||||
let Y_0 = self.Y_0.decompress().ok_or(ProofError::VerificationError)?;
|
||||
let Y_1 = self.Y_1.decompress().ok_or(ProofError::VerificationError)?;
|
||||
let Y_2 = self.Y_2.decompress().ok_or(ProofError::VerificationError)?;
|
||||
let Y_0 = self.Y_0.decompress().ok_or(ValidityProofError::FormatError)?;
|
||||
let Y_1 = self.Y_1.decompress().ok_or(ValidityProofError::FormatError)?;
|
||||
let Y_2 = self.Y_2.decompress().ok_or(ValidityProofError::FormatError)?;
|
||||
|
||||
let P_dest = elgamal_pubkey_dest.get_point();
|
||||
let P_auditor = elgamal_pubkey_auditor.get_point();
|
||||
|
@ -133,7 +133,7 @@ impl ValidityProof {
|
|||
if check.is_identity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProofError::VerificationError)
|
||||
Err(ValidityProofError::AlgebraicRelationError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,7 @@ impl ValidityProof {
|
|||
buf
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofError> {
|
||||
let bytes = array_ref![bytes, 0, 160];
|
||||
let (Y_0, Y_1, Y_2, z_r, z_x) = array_refs![bytes, 32, 32, 32, 32, 32];
|
||||
|
||||
|
@ -155,8 +155,8 @@ impl ValidityProof {
|
|||
let Y_1 = CompressedRistretto::from_slice(Y_1);
|
||||
let Y_2 = CompressedRistretto::from_slice(Y_2);
|
||||
|
||||
let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(ProofError::FormatError)?;
|
||||
let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(ProofError::FormatError)?;
|
||||
let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(ValidityProofError::FormatError)?;
|
||||
let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(ValidityProofError::FormatError)?;
|
||||
|
||||
Ok(ValidityProof {
|
||||
Y_0,
|
||||
|
|
|
@ -8,7 +8,7 @@ use {
|
|||
rand::rngs::OsRng,
|
||||
};
|
||||
use {
|
||||
crate::{errors::ProofError, transcript::TranscriptProtocol},
|
||||
crate::{sigma_proofs::errors::ZeroBalanceProofError, transcript::TranscriptProtocol},
|
||||
arrayref::{array_ref, array_refs},
|
||||
curve25519_dalek::{
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
|
@ -68,7 +68,7 @@ impl ZeroBalanceProof {
|
|||
elgamal_pubkey: &ElGamalPubkey,
|
||||
elgamal_ciphertext: &ElGamalCiphertext,
|
||||
transcript: &mut Transcript,
|
||||
) -> Result<(), ProofError> {
|
||||
) -> Result<(), ZeroBalanceProofError> {
|
||||
// extract the relevant scalar and Ristretto points from the input
|
||||
let P = elgamal_pubkey.get_point();
|
||||
let C = elgamal_ciphertext.message_comm.get_point();
|
||||
|
@ -89,8 +89,8 @@ impl ZeroBalanceProof {
|
|||
let w = transcript.challenge_scalar(b"w"); // w used for multiscalar multiplication verification
|
||||
|
||||
// decompress R or return verification error
|
||||
let Y_P = self.Y_P.decompress().ok_or(ProofError::VerificationError)?;
|
||||
let Y_D = self.Y_D.decompress().ok_or(ProofError::VerificationError)?;
|
||||
let Y_P = self.Y_P.decompress().ok_or(ZeroBalanceProofError::FormatError)?;
|
||||
let Y_D = self.Y_D.decompress().ok_or(ZeroBalanceProofError::FormatError)?;
|
||||
let z = self.z;
|
||||
|
||||
// check the required algebraic relation
|
||||
|
@ -102,7 +102,7 @@ impl ZeroBalanceProof {
|
|||
if check.is_identity() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProofError::VerificationError)
|
||||
Err(ZeroBalanceProofError::AlgebraicRelationError)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,14 +114,14 @@ impl ZeroBalanceProof {
|
|||
buf
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ZeroBalanceProofError> {
|
||||
let bytes = array_ref![bytes, 0, 96];
|
||||
let (Y_P, Y_D, z) = array_refs![bytes, 32, 32, 32];
|
||||
|
||||
let Y_P = CompressedRistretto::from_slice(Y_P);
|
||||
let Y_D = CompressedRistretto::from_slice(Y_D);
|
||||
|
||||
let z = Scalar::from_canonical_bytes(*z).ok_or(ProofError::FormatError)?;
|
||||
let z = Scalar::from_canonical_bytes(*z).ok_or(ZeroBalanceProofError::FormatError)?;
|
||||
|
||||
Ok(ZeroBalanceProof { Y_P, Y_D, z })
|
||||
}
|
||||
|
|
|
@ -21,8 +21,9 @@ mod target_arch {
|
|||
pedersen::{PedersenCommitment, PedersenDecryptHandle},
|
||||
},
|
||||
errors::ProofError,
|
||||
range_proof::RangeProof,
|
||||
range_proof::{errors::RangeProofError, RangeProof},
|
||||
sigma_proofs::{
|
||||
errors::*,
|
||||
equality_proof::EqualityProof, validity_proof::ValidityProof,
|
||||
zero_balance_proof::ZeroBalanceProof,
|
||||
},
|
||||
|
@ -151,7 +152,7 @@ mod target_arch {
|
|||
}
|
||||
|
||||
impl TryFrom<pod::EqualityProof> for EqualityProof {
|
||||
type Error = ProofError;
|
||||
type Error = EqualityProofError;
|
||||
|
||||
fn try_from(pod: pod::EqualityProof) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod.0)
|
||||
|
@ -165,7 +166,7 @@ mod target_arch {
|
|||
}
|
||||
|
||||
impl TryFrom<pod::ValidityProof> for ValidityProof {
|
||||
type Error = ProofError;
|
||||
type Error = ValidityProofError;
|
||||
|
||||
fn try_from(pod: pod::ValidityProof) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod.0)
|
||||
|
@ -179,7 +180,7 @@ mod target_arch {
|
|||
}
|
||||
|
||||
impl TryFrom<pod::ZeroBalanceProof> for ZeroBalanceProof {
|
||||
type Error = ProofError;
|
||||
type Error = ZeroBalanceProofError;
|
||||
|
||||
fn try_from(pod: pod::ZeroBalanceProof) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod.0)
|
||||
|
@ -187,11 +188,11 @@ mod target_arch {
|
|||
}
|
||||
|
||||
impl TryFrom<RangeProof> for pod::RangeProof64 {
|
||||
type Error = ProofError;
|
||||
type Error = RangeProofError;
|
||||
|
||||
fn try_from(proof: RangeProof) -> Result<Self, Self::Error> {
|
||||
if proof.ipp_proof.serialized_size() != 448 {
|
||||
return Err(ProofError::VerificationError);
|
||||
return Err(RangeProofError::FormatError);
|
||||
}
|
||||
|
||||
let mut buf = [0_u8; 672];
|
||||
|
@ -208,7 +209,7 @@ mod target_arch {
|
|||
}
|
||||
|
||||
impl TryFrom<pod::RangeProof64> for RangeProof {
|
||||
type Error = ProofError;
|
||||
type Error = RangeProofError;
|
||||
|
||||
fn try_from(pod: pod::RangeProof64) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod.0)
|
||||
|
@ -217,11 +218,11 @@ mod target_arch {
|
|||
|
||||
#[cfg(not(target_arch = "bpf"))]
|
||||
impl TryFrom<RangeProof> for pod::RangeProof128 {
|
||||
type Error = ProofError;
|
||||
type Error = RangeProofError;
|
||||
|
||||
fn try_from(proof: RangeProof) -> Result<Self, Self::Error> {
|
||||
if proof.ipp_proof.serialized_size() != 512 {
|
||||
return Err(ProofError::VerificationError);
|
||||
return Err(RangeProofError::FormatError);
|
||||
}
|
||||
|
||||
let mut buf = [0_u8; 736];
|
||||
|
@ -238,7 +239,7 @@ mod target_arch {
|
|||
}
|
||||
|
||||
impl TryFrom<pod::RangeProof128> for RangeProof {
|
||||
type Error = ProofError;
|
||||
type Error = RangeProofError;
|
||||
|
||||
fn try_from(pod: pod::RangeProof128) -> Result<Self, Self::Error> {
|
||||
Self::from_bytes(&pod.0)
|
||||
|
@ -266,7 +267,7 @@ mod tests {
|
|||
let mut transcript_create = Transcript::new(b"Test");
|
||||
let mut transcript_verify = Transcript::new(b"Test");
|
||||
|
||||
let proof = RangeProof::create(vec![55], vec![64], vec![&open], &mut transcript_create);
|
||||
let proof = RangeProof::new(vec![55], vec![64], vec![&open], &mut transcript_create);
|
||||
|
||||
let proof_serialized: pod::RangeProof64 = proof.try_into().unwrap();
|
||||
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
|
||||
|
@ -280,7 +281,7 @@ mod tests {
|
|||
.is_ok());
|
||||
|
||||
// should fail to serialize to pod::RangeProof128
|
||||
let proof = RangeProof::create(vec![55], vec![64], vec![&open], &mut transcript_create);
|
||||
let proof = RangeProof::new(vec![55], vec![64], vec![&open], &mut transcript_create);
|
||||
|
||||
assert!(TryInto::<pod::RangeProof128>::try_into(proof).is_err());
|
||||
}
|
||||
|
@ -294,7 +295,7 @@ mod tests {
|
|||
let mut transcript_create = Transcript::new(b"Test");
|
||||
let mut transcript_verify = Transcript::new(b"Test");
|
||||
|
||||
let proof = RangeProof::create(
|
||||
let proof = RangeProof::new(
|
||||
vec![55, 77, 99],
|
||||
vec![64, 32, 32],
|
||||
vec![&open_1, &open_2, &open_3],
|
||||
|
@ -317,7 +318,7 @@ mod tests {
|
|||
.is_ok());
|
||||
|
||||
// should fail to serialize to pod::RangeProof64
|
||||
let proof = RangeProof::create(
|
||||
let proof = RangeProof::new(
|
||||
vec![55, 77, 99],
|
||||
vec![64, 32, 32],
|
||||
vec![&open_1, &open_2, &open_3],
|
||||
|
|
Loading…
Reference in New Issue