sdk/crypto: Forbid PublicKey to ever be the identity point

This commit is contained in:
parazyd
2024-02-20 14:55:57 +01:00
parent dc203e12f0
commit 47e9d68ef1
10 changed files with 51 additions and 32 deletions

View File

@@ -799,7 +799,7 @@ impl Drk {
for vote in new_dao_votes {
for dao in &daos {
// TODO: we shouldn't decrypt with all DAOs here
let note = vote.0.note.decrypt_unsafe(&dao.secret_key);
let note = vote.0.note.decrypt_unsafe(&dao.secret_key)?;
eprintln!("Managed to decrypt DAO proposal vote note");
let daos_proposals = self.get_dao_proposals(dao.id).await?;
let mut proposal_id = None;

View File

@@ -72,7 +72,7 @@ impl DaoAuthMoneyTransferCall {
coin_attrs.blind.inner(),
];
let enc_note =
ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &coin_attrs.public_key);
ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &coin_attrs.public_key)?;
let prover_witnesses = vec![
Witness::EcNiPoint(Value::known(coin_attrs.public_key.inner())),
@@ -117,8 +117,9 @@ impl DaoAuthMoneyTransferCall {
self.dao_coin_attrs.token_id.inner(),
self.dao_coin_attrs.blind.inner(),
];
let dao_change_attrs =
ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao.public_key);
ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao.public_key)?;
let params = DaoAuthMoneyTransferParams { enc_attrs, dao_change_attrs };

View File

@@ -250,7 +250,7 @@ impl DaoVoteCall {
let note = [vote_option, yes_vote_blind.inner(), all_vote_value_fp, all_vote_blind.inner()];
let enc_note =
ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao_keypair.public);
ElGamalEncryptedNote::encrypt_unsafe(note, &ephem_secret, &self.dao_keypair.public)?;
let public_inputs = vec![
token_commit,

View File

@@ -392,9 +392,9 @@ fn integration_test() -> Result<()> {
}
// Gather and decrypt all vote notes
let vote_note_1 = alice_vote_params.note.decrypt_unsafe(&dao_keypair.secret);
let vote_note_2 = bob_vote_params.note.decrypt_unsafe(&dao_keypair.secret);
let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(&dao_keypair.secret);
let vote_note_1 = alice_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
let vote_note_2 = bob_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
let vote_note_3 = charlie_vote_params.note.decrypt_unsafe(&dao_keypair.secret).unwrap();
// Count the votes
let mut total_yes_vote_value = 0;

View File

@@ -20,18 +20,19 @@ use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use pasta_curves::group::{GroupEncoding, Wnaf};
use super::{util::fp_mod_fv, PublicKey, SecretKey};
use crate::error::ContractError;
pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"DarkFiSaplingKDF";
/// Sapling key agreement for note encryption.
/// Implements section 5.4.4.3 of the Zcash Protocol Specification
pub fn sapling_ka_agree(esk: &SecretKey, pk_d: &PublicKey) -> PublicKey {
pub fn sapling_ka_agree(esk: &SecretKey, pk_d: &PublicKey) -> Result<PublicKey, ContractError> {
let esk_s = fp_mod_fv(esk.inner());
// Windowed multiplication is constant time. Hence that is used here vs naive EC mult.
// Decrypting notes is a an amortized operation, so you want successful rare-case note
// decryptions to be indistinguishable from the usual case.
let mut wnaf = Wnaf::new();
PublicKey::from(wnaf.scalar(&esk_s).base(pk_d.inner()))
PublicKey::try_from(wnaf.scalar(&esk_s).base(pk_d.inner()))
}
/// Sapling KDF for note encryption.

View File

@@ -54,7 +54,6 @@ impl VrfProof {
/// and a seed input `alpha_string`.
pub fn prove(x: SecretKey, alpha_string: &[u8]) -> Self {
let Y = PublicKey::from_secret(x);
assert!(!bool::from(Y.inner().is_identity()));
let mut message = vec![];
message.extend_from_slice(&Y.to_bytes());
@@ -86,10 +85,6 @@ impl VrfProof {
/// Verify a `VrfProof` given a `Publickey` and a seed input `alpha_string`.
pub fn verify(&self, Y: PublicKey, alpha_string: &[u8]) -> bool {
if bool::from(Y.inner().is_identity()) {
return false
}
let mut message = vec![];
message.extend_from_slice(&Y.to_bytes());
message.extend_from_slice(alpha_string);

View File

@@ -26,7 +26,7 @@ use pasta_curves::{
arithmetic::CurveAffine,
group::{
ff::{Field, PrimeField},
Curve, GroupEncoding,
Curve, Group, GroupEncoding,
},
pallas,
};
@@ -136,8 +136,18 @@ impl PublicKey {
/// Instantiate a `PublicKey` given 32 bytes. Returns an error
/// if the representation is noncanonical.
pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, ContractError> {
match pallas::Point::from_bytes(&bytes).into() {
Some(k) => Ok(Self(k)),
match <subtle::CtOption<pallas::Point> as Into<Option<pallas::Point>>>::into(
pallas::Point::from_bytes(&bytes),
) {
Some(k) => {
if bool::from(k.is_identity()) {
return Err(ContractError::IoError(
"Could not convert bytes to PublicKey".to_string(),
))
}
Ok(Self(k))
}
None => Err(ContractError::IoError("Could not convert bytes to PublicKey".to_string())),
}
}
@@ -164,9 +174,17 @@ impl PublicKey {
}
}
impl From<pallas::Point> for PublicKey {
fn from(x: pallas::Point) -> Self {
Self(x)
impl TryFrom<pallas::Point> for PublicKey {
type Error = ContractError;
fn try_from(x: pallas::Point) -> Result<Self, Self::Error> {
if bool::from(x.is_identity()) {
return Err(ContractError::IoError(
"Could not convert identity point to PublicKey".to_string(),
))
}
Ok(Self(x))
}
}

View File

@@ -45,7 +45,7 @@ impl AeadEncryptedNote {
) -> Result<Self, ContractError> {
let ephem_secret = SecretKey::random(rng);
let ephem_public = PublicKey::from_secret(ephem_secret);
let shared_secret = diffie_hellman::sapling_ka_agree(&ephem_secret, public);
let shared_secret = diffie_hellman::sapling_ka_agree(&ephem_secret, public)?;
let key = diffie_hellman::kdf_sapling(&shared_secret, &ephem_public);
let mut input = Vec::new();
@@ -63,7 +63,7 @@ impl AeadEncryptedNote {
}
pub fn decrypt<D: Decodable>(&self, secret: &SecretKey) -> Result<D, ContractError> {
let shared_secret = diffie_hellman::sapling_ka_agree(secret, &self.ephem_public);
let shared_secret = diffie_hellman::sapling_ka_agree(secret, &self.ephem_public)?;
let key = diffie_hellman::kdf_sapling(&shared_secret, &self.ephem_public);
let ct_len = self.ciphertext.len();
@@ -104,10 +104,11 @@ impl<const N: usize> ElGamalEncryptedNote<N> {
values: [pallas::Base; N],
ephem_secret: &SecretKey,
public: &PublicKey,
) -> Self {
) -> Result<Self, ContractError> {
// Derive shared secret using DH
let ephem_public = PublicKey::from_secret(*ephem_secret);
let (ss_x, ss_y) = PublicKey::from(public.inner() * fp_mod_fv(ephem_secret.inner())).xy();
let (ss_x, ss_y) =
PublicKey::try_from(public.inner() * fp_mod_fv(ephem_secret.inner()))?.xy();
let shared_secret = poseidon_hash([ss_x, ss_y]);
// Derive the blinds using the shared secret and incremental nonces
@@ -122,7 +123,7 @@ impl<const N: usize> ElGamalEncryptedNote<N> {
encrypted_values[i] = values[i] + blinds[i];
}
Self { encrypted_values, ephem_public }
Ok(Self { encrypted_values, ephem_public })
}
/// Decrypt the `ElGamalEncryptedNote` using a `SecretKey` for shared secret derivation
@@ -131,10 +132,10 @@ impl<const N: usize> ElGamalEncryptedNote<N> {
/// Note that this does not do any message authentication.
/// This means that alterations of the ciphertexts lead to the same alterations
/// on the plaintexts.
pub fn decrypt_unsafe(&self, secret: &SecretKey) -> [pallas::Base; N] {
pub fn decrypt_unsafe(&self, secret: &SecretKey) -> Result<[pallas::Base; N], ContractError> {
// Derive shared secret using DH
let (ss_x, ss_y) =
PublicKey::from(self.ephem_public.inner() * fp_mod_fv(secret.inner())).xy();
PublicKey::try_from(self.ephem_public.inner() * fp_mod_fv(secret.inner()))?.xy();
let shared_secret = poseidon_hash([ss_x, ss_y]);
let mut blinds = [pallas::Base::ZERO; N];
@@ -147,7 +148,7 @@ impl<const N: usize> ElGamalEncryptedNote<N> {
decrypted_values[i] = self.encrypted_values[i] - blinds[i];
}
decrypted_values
Ok(decrypted_values)
}
}
@@ -181,9 +182,10 @@ mod tests {
let ephem_secret = SecretKey::random(&mut OsRng);
let encrypted_note =
ElGamalEncryptedNote::encrypt_unsafe(plain_values, &ephem_secret, &keypair.public);
ElGamalEncryptedNote::encrypt_unsafe(plain_values, &ephem_secret, &keypair.public)
.unwrap();
let decrypted_values = encrypted_note.decrypt_unsafe(&keypair.secret);
let decrypted_values = encrypted_note.decrypt_unsafe(&keypair.secret).unwrap();
assert_eq!(plain_values, decrypted_values);
}

View File

@@ -117,7 +117,8 @@ fn halo2_vk_ser() -> Result<()> {
let ephem_secret = SecretKey::random(&mut OsRng);
let pubkey = PublicKey::from_secret(ephem_secret).inner();
let (ephem_x, ephem_y) = PublicKey::from(pubkey * fp_mod_fv(ephem_secret.inner())).xy();
let (ephem_x, ephem_y) =
PublicKey::try_from(pubkey * fp_mod_fv(ephem_secret.inner())).unwrap().xy();
let prover_witnesses = vec![
Witness::Base(Value::known(pallas::Base::from(value))),
Witness::Scalar(Value::known(value_blind.inner())),

View File

@@ -79,7 +79,8 @@ fn zkvm_opcodes() -> Result<()> {
let ephem_secret = SecretKey::random(&mut OsRng);
let pubkey = PublicKey::from_secret(ephem_secret).inner();
let (ephem_x, ephem_y) = PublicKey::from(pubkey * fp_mod_fv(ephem_secret.inner())).xy();
let (ephem_x, ephem_y) =
PublicKey::try_from(pubkey * fp_mod_fv(ephem_secret.inner())).unwrap().xy();
let prover_witnesses = vec![
Witness::Base(Value::known(pallas::Base::from(value))),