Cleanup which makes transcript optional, only required for multisig

This commit is contained in:
Luke Parker 2022-05-03 08:49:46 -04:00
parent 56fc39fff5
commit 9a42391b75
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
12 changed files with 167 additions and 161 deletions

View file

@ -17,12 +17,11 @@ blake2 = "0.10"
curve25519-dalek = { version = "3.2", features = ["std", "simd_backend"] } curve25519-dalek = { version = "3.2", features = ["std", "simd_backend"] }
transcript = { path = "../../crypto/transcript" }
ff = { version = "0.11", optional = true } ff = { version = "0.11", optional = true }
group = { version = "0.11", optional = true } group = { version = "0.11", optional = true }
dalek-ff-group = { path = "../../crypto/dalek-ff-group", optional = true } transcript = { path = "../../crypto/transcript", optional = true }
frost = { path = "../../crypto/frost", optional = true } frost = { path = "../../crypto/frost", optional = true }
dalek-ff-group = { path = "../../crypto/dalek-ff-group", optional = true }
# Locked to this specific patch version due to a bug we compensate for # Locked to this specific patch version due to a bug we compensate for
monero = { version = "0.16.0", features = ["experimental"] } monero = { version = "0.16.0", features = ["experimental"] }
@ -34,7 +33,7 @@ monero-epee-bin-serde = "1.0"
reqwest = { version = "0.11", features = ["json"] } reqwest = { version = "0.11", features = ["json"] }
[features] [features]
multisig = ["ff", "group", "dalek-ff-group", "frost"] multisig = ["ff", "group", "transcript", "frost", "dalek-ff-group"]
[dev-dependencies] [dev-dependencies]
rand = "0.8" rand = "0.8"

View file

@ -113,8 +113,8 @@ pub(crate) fn sign_core<R: RngCore + CryptoRng>(
let mut to_hash = vec![]; let mut to_hash = vec![];
to_hash.reserve_exact(((2 * n) + 4) * 32); to_hash.reserve_exact(((2 * n) + 4) * 32);
const PREFIX: &str = "CLSAG_"; const PREFIX: &str = "CLSAG_";
const AGG_0: &str = "CLSAG_agg_0"; const AGG_0: &str = "CLSAG_agg_0";
const ROUND: &str = "round"; const ROUND: &str = "round";
to_hash.extend(AGG_0.bytes()); to_hash.extend(AGG_0.bytes());
to_hash.extend([0; 32 - AGG_0.len()]); to_hash.extend([0; 32 - AGG_0.len()]);

View file

@ -14,14 +14,13 @@ use monero::util::ringct::{Key, Clsag};
use group::Group; use group::Group;
use dalek_ff_group as dfg;
use transcript::Transcript as TranscriptTrait; use transcript::Transcript as TranscriptTrait;
use frost::{Curve, FrostError, algorithm::Algorithm, MultisigView}; use frost::{Curve, FrostError, algorithm::Algorithm, MultisigView};
use dalek_ff_group as dfg;
use crate::{ use crate::{
Transcript,
hash_to_point, hash_to_point,
frost::{MultisigError, Ed25519, DLEqProof}, frost::{Transcript, MultisigError, Ed25519, DLEqProof},
key_image, key_image,
clsag::{Input, sign_core, verify} clsag::{Input, sign_core, verify}
}; };

View file

@ -16,11 +16,14 @@ use curve25519_dalek::{
use ff::PrimeField; use ff::PrimeField;
use group::Group; use group::Group;
use dalek_ff_group as dfg; use transcript::DigestTranscript;
use frost::{CurveError, Curve}; use frost::{CurveError, Curve};
use dalek_ff_group as dfg;
use crate::random_scalar; use crate::random_scalar;
pub(crate) type Transcript = DigestTranscript::<blake2::Blake2b512>;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum MultisigError { pub enum MultisigError {
#[error("internal error ({0})")] #[error("internal error ({0})")]

View file

@ -12,8 +12,6 @@ use curve25519_dalek::{
use monero::util::key::H; use monero::util::key::H;
use transcript::DigestTranscript;
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
pub mod frost; pub mod frost;
@ -39,19 +37,10 @@ extern "C" {
) -> bool; ) -> bool;
} }
// Allows using a modern rand as dalek's is notoriously dated
pub fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Scalar {
let mut r = [0; 64];
rng.fill_bytes(&mut r);
Scalar::from_bytes_mod_order_wide(&r)
}
lazy_static! { lazy_static! {
static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H.point.decompress().unwrap()); static ref H_TABLE: EdwardsBasepointTable = EdwardsBasepointTable::create(&H.point.decompress().unwrap());
} }
pub(crate) type Transcript = DigestTranscript::<blake2::Blake2b512>;
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Commitment { pub struct Commitment {
@ -73,6 +62,13 @@ impl Commitment {
} }
} }
// Allows using a modern rand as dalek's is notoriously dated
pub fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Scalar {
let mut r = [0; 64];
rng.fill_bytes(&mut r);
Scalar::from_bytes_mod_order_wide(&r)
}
pub fn hash(data: &[u8]) -> [u8; 32] { pub fn hash(data: &[u8]) -> [u8; 32] {
let mut keccak = Keccak::v256(); let mut keccak = Keccak::v256();
keccak.update(data); keccak.update(data);
@ -87,8 +83,6 @@ pub fn hash_to_scalar(data: &[u8]) -> Scalar {
pub fn hash_to_point(point: &EdwardsPoint) -> EdwardsPoint { pub fn hash_to_point(point: &EdwardsPoint) -> EdwardsPoint {
let mut bytes = point.compress().to_bytes(); let mut bytes = point.compress().to_bytes();
unsafe { unsafe { c_hash_to_point(bytes.as_mut_ptr()); }
c_hash_to_point(bytes.as_mut_ptr());
}
CompressedEdwardsY::from_slice(&bytes).decompress().unwrap() CompressedEdwardsY::from_slice(&bytes).decompress().unwrap()
} }

View file

@ -1,4 +1,4 @@
// TOOD // TODO
pub(crate) fn select(o: u64) -> (u8, Vec<u64>) { pub(crate) fn select(o: u64) -> (u8, Vec<u64>) {
let mut mixins: Vec<u64> = (o .. o + 11).into_iter().collect(); let mut mixins: Vec<u64> = (o .. o + 11).into_iter().collect();
mixins.sort(); mixins.sort();

View file

@ -24,13 +24,10 @@ use monero::{
} }
}; };
use transcript::Transcript as TranscriptTrait;
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
use frost::FrostError; use frost::FrostError;
use crate::{ use crate::{
Transcript,
Commitment, Commitment,
random_scalar, random_scalar,
hash, hash_to_scalar, hash, hash_to_scalar,
@ -46,8 +43,6 @@ mod multisig;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum TransactionError { pub enum TransactionError {
#[error("invalid preparation ({0})")]
InvalidPreparation(String),
#[error("no inputs")] #[error("no inputs")]
NoInputs, NoInputs,
#[error("no outputs")] #[error("no outputs")]
@ -196,17 +191,14 @@ impl Output {
} }
} }
enum Preparation<'a, R: RngCore + CryptoRng> {
Leader(&'a mut R),
Follower([u8; 32], Bulletproof)
}
async fn prepare_inputs( async fn prepare_inputs(
rpc: &Rpc, rpc: &Rpc,
spend: &Scalar, spend: &Scalar,
inputs: &[SpendableOutput], inputs: &[SpendableOutput],
tx: &mut Transaction tx: &mut Transaction
) -> Result<Vec<(Scalar, clsag::Input, EdwardsPoint)>, TransactionError> { ) -> Result<Vec<(Scalar, clsag::Input, EdwardsPoint)>, TransactionError> {
// TODO sort inputs
let mut signable = Vec::with_capacity(inputs.len()); let mut signable = Vec::with_capacity(inputs.len());
for (i, input) in inputs.iter().enumerate() { for (i, input) in inputs.iter().enumerate() {
// Select mixins // Select mixins
@ -238,7 +230,10 @@ pub struct SignableTransaction {
inputs: Vec<SpendableOutput>, inputs: Vec<SpendableOutput>,
payments: Vec<(Address, u64)>, payments: Vec<(Address, u64)>,
change: Address, change: Address,
fee_per_byte: u64 fee_per_byte: u64,
fee: u64,
outputs: Vec<Output>
} }
impl SignableTransaction { impl SignableTransaction {
@ -260,25 +255,25 @@ impl SignableTransaction {
inputs, inputs,
payments, payments,
change, change,
fee_per_byte fee_per_byte,
fee: 0,
outputs: vec![]
} }
) )
} }
// This could be refactored so prep, a multisig-required variable, is used only by multisig fn prepare_outputs<R: RngCore + CryptoRng>(
// Not shimmed by the single signer API as well &mut self,
// This would enable moving Transcript as a whole to the multisig feature rng: &mut R
fn prepare_outputs<'a, R: RngCore + CryptoRng>( ) -> Result<(Vec<Commitment>, Scalar), TransactionError> {
&self, self.fee = self.fee_per_byte * 2000; // TODO
prep: &mut Preparation<'a, R>
) -> Result<(Vec<u8>, Scalar, Transaction), TransactionError> {
let fee = self.fee_per_byte * 2000; // TODO
// TODO TX MAX SIZE // TODO TX MAX SIZE
// Make sure we have enough funds // Make sure we have enough funds
let in_amount = self.inputs.iter().map(|input| input.commitment.amount).sum(); let in_amount = self.inputs.iter().map(|input| input.commitment.amount).sum();
let out_amount = fee + self.payments.iter().map(|payment| payment.1).sum::<u64>(); let out_amount = self.fee + self.payments.iter().map(|payment| payment.1).sum::<u64>();
if in_amount < out_amount { if in_amount < out_amount {
Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?; Err(TransactionError::NotEnoughFunds(in_amount, out_amount))?;
} }
@ -287,122 +282,83 @@ impl SignableTransaction {
let mut payments = self.payments.clone(); let mut payments = self.payments.clone();
payments.push((self.change, in_amount - out_amount)); payments.push((self.change, in_amount - out_amount));
// Grab the prep // TODO randomly sort outputs
let mut entropy = [0; 32];
let mut bp = None;
match prep {
Preparation::Leader(ref mut rng) => {
// The Leader generates the entropy for the one time keys and the bulletproof
// This prevents de-anonymization via recalculation of the randomness which is deterministic
rng.fill_bytes(&mut entropy);
},
Preparation::Follower(e, b) => {
entropy = e.clone();
bp = Some(b.clone());
}
}
let mut transcript = Transcript::new(b"StealthAddress"); self.outputs.clear();
// This output can only be spent once. Therefore, it forces all one time keys used here to be self.outputs = Vec::with_capacity(payments.len());
// unique, even if the leader reuses entropy. While another transaction could use a different
// input ordering to swap which 0 is, that input set can't contain this input without being a
// double spend
transcript.append_message(b"hash", &self.inputs[0].tx.0);
transcript.append_message(b"index", &u64::try_from(self.inputs[0].o).unwrap().to_le_bytes());
let mut rng = transcript.seeded_rng(b"tx_keys", Some(entropy));
let mut outputs = Vec::with_capacity(payments.len());
let mut commitments = Vec::with_capacity(payments.len()); let mut commitments = Vec::with_capacity(payments.len());
for o in 0 .. payments.len() { for o in 0 .. payments.len() {
outputs.push(Output::new(&mut rng, payments[o], o)?); self.outputs.push(Output::new(rng, payments[o], o)?);
commitments.push(Commitment::new(outputs[o].mask, payments[o].1)); commitments.push(Commitment::new(self.outputs[o].mask, payments[o].1));
} }
if bp.is_none() { Ok((commitments, self.outputs.iter().map(|output| output.mask).sum()))
// Generate the bulletproof if leader }
bp = Some(bulletproofs::generate(&commitments)?);
} else {
// Verify the bulletproof if follower
if !bulletproofs::verify(
bp.as_ref().unwrap(),
&commitments.iter().map(|c| c.calculate()).collect::<Vec<EdwardsPoint>>()
) {
Err(TransactionError::InvalidPreparation("invalid bulletproof".to_string()))?;
}
}
fn prepare_transaction(
&self,
commitments: &[Commitment],
bp: Bulletproof
) -> Transaction {
// Create the TX extra // Create the TX extra
let mut extra = ExtraField(vec![ let mut extra = ExtraField(vec![
SubField::TxPublicKey(PublicKey { point: outputs[0].R.compress() }) SubField::TxPublicKey(PublicKey { point: self.outputs[0].R.compress() })
]); ]);
extra.0.push(SubField::AdditionalPublickKey( extra.0.push(SubField::AdditionalPublickKey(
outputs[1 .. outputs.len()].iter().map(|output| PublicKey { point: output.R.compress() }).collect() self.outputs[1 .. self.outputs.len()].iter().map(|output| PublicKey { point: output.R.compress() }).collect()
)); ));
// Format it for monero-rs // Format it for monero-rs
let mut mrs_outputs = Vec::with_capacity(outputs.len()); let mut mrs_outputs = Vec::with_capacity(self.outputs.len());
let mut out_pk = Vec::with_capacity(outputs.len()); let mut out_pk = Vec::with_capacity(self.outputs.len());
let mut ecdh_info = Vec::with_capacity(outputs.len()); let mut ecdh_info = Vec::with_capacity(self.outputs.len());
for o in 0 .. outputs.len() { for o in 0 .. self.outputs.len() {
mrs_outputs.push(TxOut { mrs_outputs.push(TxOut {
amount: VarInt(0), amount: VarInt(0),
target: TxOutTarget::ToKey { key: PublicKey { point: outputs[o].dest.compress() } } target: TxOutTarget::ToKey { key: PublicKey { point: self.outputs[o].dest.compress() } }
}); });
out_pk.push(CtKey { out_pk.push(CtKey {
mask: Key { key: commitments[o].calculate().compress().to_bytes() } mask: Key { key: commitments[o].calculate().compress().to_bytes() }
}); });
ecdh_info.push(EcdhInfo::Bulletproof { amount: outputs[o].amount }); ecdh_info.push(EcdhInfo::Bulletproof { amount: self.outputs[o].amount });
} }
Ok(( Transaction {
match prep { prefix: TransactionPrefix {
// Encode the prep version: VarInt(2),
Preparation::Leader(..) => { unlock_time: VarInt(0),
let mut prep = entropy.to_vec(); inputs: vec![],
bp.as_ref().unwrap().consensus_encode(&mut prep).expect("Couldn't encode bulletproof"); outputs: mrs_outputs,
prep extra
},
Preparation::Follower(..) => {
vec![]
}
}, },
outputs.iter().map(|output| output.mask).sum(), signatures: vec![],
Transaction { rct_signatures: RctSig {
prefix: TransactionPrefix { sig: Some(RctSigBase {
version: VarInt(2), rct_type: RctType::Clsag,
unlock_time: VarInt(0), txn_fee: VarInt(self.fee),
inputs: vec![], pseudo_outs: vec![],
outputs: mrs_outputs, ecdh_info,
extra out_pk
}, }),
signatures: vec![], p: Some(RctSigPrunable {
rct_signatures: RctSig { range_sigs: vec![],
sig: Some(RctSigBase { bulletproofs: vec![bp],
rct_type: RctType::Clsag, MGs: vec![],
txn_fee: VarInt(fee), Clsags: vec![],
pseudo_outs: vec![], pseudo_outs: vec![]
ecdh_info, })
out_pk
}),
p: Some(RctSigPrunable {
range_sigs: vec![],
bulletproofs: vec![bp.unwrap()],
MGs: vec![],
Clsags: vec![],
pseudo_outs: vec![]
})
}
} }
)) }
} }
pub async fn sign<R: RngCore + CryptoRng>( pub async fn sign<R: RngCore + CryptoRng>(
&self, &mut self,
rng: &mut R, rng: &mut R,
rpc: &Rpc, rpc: &Rpc,
spend: &Scalar spend: &Scalar
) -> Result<Transaction, TransactionError> { ) -> Result<Transaction, TransactionError> {
let (_, mask_sum, mut tx) = self.prepare_outputs(&mut Preparation::Leader(rng))?; let (commitments, mask_sum) = self.prepare_outputs(rng)?;
let mut tx = self.prepare_transaction(&commitments, bulletproofs::generate(&commitments)?);
let signable = prepare_inputs(rpc, spend, &self.inputs, &mut tx).await?; let signable = prepare_inputs(rpc, spend, &self.inputs, &mut tx).await?;

View file

@ -6,7 +6,7 @@ use curve25519_dalek::{scalar::Scalar, edwards::{EdwardsPoint, CompressedEdwards
use monero::{ use monero::{
Hash, VarInt, Hash, VarInt,
consensus::deserialize, consensus::{Encodable, deserialize},
util::ringct::Key, util::ringct::Key,
blockdata::transaction::{KeyImage, TxIn, Transaction} blockdata::transaction::{KeyImage, TxIn, Transaction}
}; };
@ -15,12 +15,10 @@ use transcript::Transcript as TranscriptTrait;
use frost::{FrostError, MultisigKeys, MultisigParams, sign::{State, StateMachine, AlgorithmMachine}}; use frost::{FrostError, MultisigKeys, MultisigParams, sign::{State, StateMachine, AlgorithmMachine}};
use crate::{ use crate::{
Transcript, frost::{Transcript, Ed25519},
frost::Ed25519, key_image, bulletproofs, clsag,
key_image,
clsag,
rpc::Rpc, rpc::Rpc,
transaction::{TransactionError, Preparation, SignableTransaction, mixins} transaction::{TransactionError, SignableTransaction, mixins}
}; };
pub struct TransactionMachine { pub struct TransactionMachine {
@ -36,7 +34,7 @@ pub struct TransactionMachine {
impl SignableTransaction { impl SignableTransaction {
pub async fn multisig<R: RngCore + CryptoRng>( pub async fn multisig<R: RngCore + CryptoRng>(
self, mut self,
rng: &mut R, rng: &mut R,
rpc: &Rpc, rpc: &Rpc,
keys: Rc<MultisigKeys<Ed25519>>, keys: Rc<MultisigKeys<Ed25519>>,
@ -83,7 +81,7 @@ impl SignableTransaction {
} }
// Verify these outputs by a dummy prep // Verify these outputs by a dummy prep
self.prepare_outputs(&mut Preparation::Leader(rng))?; self.prepare_outputs(rng)?;
Ok(TransactionMachine { Ok(TransactionMachine {
leader: keys.params().i() == included[0], leader: keys.params().i() == included[0],
@ -98,6 +96,18 @@ impl SignableTransaction {
} }
} }
// Seeded RNG so multisig participants agree on one time keys to use, preventing burning attacks
fn outputs_rng(tx: &SignableTransaction, entropy: [u8; 32]) -> <Transcript as TranscriptTrait>::SeededRng {
let mut transcript = Transcript::new(b"StealthAddress");
// This output can only be spent once. Therefore, it forces all one time keys used here to be
// unique, even if the entropy is reused. While another transaction could use a different input
// ordering to swap which 0 is, that input set can't contain this input without being a double
// spend
transcript.append_message(b"hash", &tx.inputs[0].tx.0);
transcript.append_message(b"index", &u64::try_from(tx.inputs[0].o).unwrap().to_le_bytes());
transcript.seeded_rng(b"tx_keys", Some(entropy))
}
impl StateMachine for TransactionMachine { impl StateMachine for TransactionMachine {
type Signature = Transaction; type Signature = Transaction;
@ -116,11 +126,20 @@ impl StateMachine for TransactionMachine {
} }
if self.leader { if self.leader {
let (prep, mask_sum, tx) = self.signable.prepare_outputs(&mut Preparation::Leader(rng)).unwrap(); let mut entropy = [0; 32];
self.mask_sum.replace(mask_sum); rng.fill_bytes(&mut entropy);
self.tx = Some(tx); serialized.extend(&entropy);
serialized.extend(&prep); let mut rng = outputs_rng(&self.signable, entropy);
// Safe to unwrap thanks to the dummy prepare
let (commitments, mask_sum) = self.signable.prepare_outputs(&mut rng).unwrap();
self.mask_sum.replace(mask_sum);
let bp = bulletproofs::generate(&commitments).unwrap();
bp.consensus_encode(&mut serialized).unwrap();
let tx = self.signable.prepare_transaction(&commitments, bp);
self.tx = Some(tx);
} }
Ok(serialized) Ok(serialized)
@ -150,14 +169,21 @@ impl StateMachine for TransactionMachine {
} }
let prep = prep.as_ref().unwrap(); let prep = prep.as_ref().unwrap();
// Handle the prep with a seeded RNG type to make rustc happy let mut rng = outputs_rng(
let (_, mask_sum, tx_inner) = self.signable.prepare_outputs::<<Transcript as TranscriptTrait>::SeededRng>( &self.signable,
&mut Preparation::Follower( prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidShare(l))?
prep[clsag_lens .. (clsag_lens + 32)].try_into().map_err(|_| FrostError::InvalidCommitment(l))?, );
deserialize(&prep[(clsag_lens + 32) .. prep.len()]).map_err(|_| FrostError::InvalidCommitment(l))? // Not invalid outputs due to doing a dummy prep as leader
) let (commitments, mask_sum) = self.signable.prepare_outputs(&mut rng).map_err(|_| FrostError::InvalidShare(l))?;
).map_err(|_| FrostError::InvalidShare(l))?; // Not invalid outputs due to doing a dummy prep as leader
self.mask_sum.replace(mask_sum); self.mask_sum.replace(mask_sum);
// Verify the provided bulletproofs if not leader
let bp = deserialize(&prep[(clsag_lens + 32) .. prep.len()]).map_err(|_| FrostError::InvalidShare(l))?;
if !bulletproofs::verify(&bp, &commitments.iter().map(|c| c.calculate()).collect::<Vec<EdwardsPoint>>()) {
Err(FrostError::InvalidShare(l))?;
}
let tx_inner = self.signable.prepare_transaction(&commitments, bp);
tx = Some(tx_inner); tx = Some(tx_inner);
break; break;
} }
@ -188,6 +214,8 @@ impl StateMachine for TransactionMachine {
}; };
} }
// TODO sort inputs
let mut tx = tx.unwrap(); let mut tx = tx.unwrap();
tx.prefix.inputs = self.inputs.clone(); tx.prefix.inputs = self.inputs.clone();
self.msg.replace(tx.signature_hash().unwrap().0); self.msg.replace(tx.signature_hash().unwrap().0);

View file

@ -1,10 +1,13 @@
#[cfg(feature = "multisig")]
use std::{rc::Rc, cell::RefCell}; use std::{rc::Rc, cell::RefCell};
use rand::{RngCore, rngs::OsRng}; use rand::{RngCore, rngs::OsRng};
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar}; use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
use monero_serai::{random_scalar, Commitment, frost::MultisigError, key_image, clsag}; use monero_serai::{random_scalar, Commitment, key_image, clsag};
#[cfg(feature = "multisig")]
use monero_serai::frost::MultisigError;
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]
mod frost; mod frost;

View file

@ -120,7 +120,9 @@ impl Field for Scalar {
fn one() -> Self { Self(DScalar::one()) } fn one() -> Self { Self(DScalar::one()) }
fn square(&self) -> Self { *self * self } fn square(&self) -> Self { *self * self }
fn double(&self) -> Self { *self + self } fn double(&self) -> Self { *self + self }
fn invert(&self) -> CtOption<Self> { CtOption::new(Self(self.0.invert()), Choice::from(1 as u8)) } fn invert(&self) -> CtOption<Self> {
CtOption::new(Self(self.0.invert()), Choice::from(1 as u8))
}
fn sqrt(&self) -> CtOption<Self> { unimplemented!() } fn sqrt(&self) -> CtOption<Self> { unimplemented!() }
fn is_zero(&self) -> Choice { Choice::from(if self.0 == DScalar::zero() { 1 } else { 0 }) } fn is_zero(&self) -> Choice { Choice::from(if self.0 == DScalar::zero() { 1 } else { 0 }) }
fn cube(&self) -> Self { *self * self * self } fn cube(&self) -> Self { *self * self * self }
@ -137,7 +139,10 @@ impl PrimeField for Scalar {
const CAPACITY: u32 = 252; const CAPACITY: u32 = 252;
fn from_repr(bytes: [u8; 32]) -> CtOption<Self> { fn from_repr(bytes: [u8; 32]) -> CtOption<Self> {
let scalar = DScalar::from_canonical_bytes(bytes).map(|x| Scalar(x)); let scalar = DScalar::from_canonical_bytes(bytes).map(|x| Scalar(x));
CtOption::new(scalar.unwrap_or(Scalar::zero()), Choice::from(if scalar.is_some() { 1 } else { 0 })) CtOption::new(
scalar.unwrap_or(Scalar::zero()),
Choice::from(if scalar.is_some() { 1 } else { 0 })
)
} }
fn to_repr(&self) -> [u8; 32] { self.0.to_bytes() } fn to_repr(&self) -> [u8; 32] { self.0.to_bytes() }
@ -285,7 +290,9 @@ impl EdwardsPoint {
} }
pub struct EdwardsBasepointTable(pub DTable); pub struct EdwardsBasepointTable(pub DTable);
pub const ED25519_BASEPOINT_TABLE: EdwardsBasepointTable = EdwardsBasepointTable(constants::ED25519_BASEPOINT_TABLE); pub const ED25519_BASEPOINT_TABLE: EdwardsBasepointTable = EdwardsBasepointTable(
constants::ED25519_BASEPOINT_TABLE
);
impl Deref for EdwardsBasepointTable { impl Deref for EdwardsBasepointTable {
type Target = DTable; type Target = DTable;

View file

@ -54,8 +54,8 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug {
fn generator_table() -> Self::T; fn generator_table() -> Self::T;
/// Multiexponentation function, presumably Straus or Pippenger /// Multiexponentation function, presumably Straus or Pippenger
/// This library does provide an implementation of Straus which should increase key generation /// This library does forward an implementation of Straus which should increase key generation
/// performance by around 4x, also named multiexp_vartime, with the same API. However, if a more /// performance by around 4x, also named multiexp_vartime, with a similar API. However, if a more
/// performant implementation is available, that should be used instead /// performant implementation is available, that should be used instead
// This could also be written as -> Option<C::G> with None for not implemented // This could also be written as -> Option<C::G> with None for not implemented
fn multiexp_vartime(scalars: &[Self::F], points: &[Self::G]) -> Self::G; fn multiexp_vartime(scalars: &[Self::F], points: &[Self::G]) -> Self::G;

View file

@ -16,7 +16,11 @@ pub trait Transcript {
fn new(label: &'static [u8]) -> Self; fn new(label: &'static [u8]) -> Self;
fn append_message(&mut self, label: &'static [u8], message: &[u8]); fn append_message(&mut self, label: &'static [u8], message: &[u8]);
fn challenge(&mut self, label: &'static [u8], len: usize) -> Vec<u8>; fn challenge(&mut self, label: &'static [u8], len: usize) -> Vec<u8>;
fn seeded_rng(&self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> Self::SeededRng; fn seeded_rng(
&self,
label: &'static [u8],
additional_entropy: Option<[u8; 32]>
) -> Self::SeededRng;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -40,15 +44,28 @@ impl<D: Digest> Transcript for DigestTranscript<D> {
self.0.extend(label); self.0.extend(label);
let mut challenge = Vec::with_capacity(len); let mut challenge = Vec::with_capacity(len);
challenge.extend(&D::new().chain_update(&self.0).chain_update(&0u64.to_le_bytes()).finalize()); challenge.extend(
&D::new()
.chain_update(&self.0)
.chain_update(&0u64.to_le_bytes()).finalize()
);
for i in 0 .. (len / challenge.len()) { for i in 0 .. (len / challenge.len()) {
challenge.extend(&D::new().chain_update(&self.0).chain_update(&u64::try_from(i).unwrap().to_le_bytes()).finalize()); challenge.extend(
&D::new()
.chain_update(&self.0)
.chain_update(&u64::try_from(i).unwrap().to_le_bytes())
.finalize()
);
} }
challenge.truncate(len); challenge.truncate(len);
challenge challenge
} }
fn seeded_rng(&self, label: &'static [u8], additional_entropy: Option<[u8; 32]>) -> Self::SeededRng { fn seeded_rng(
&self,
label: &'static [u8],
additional_entropy: Option<[u8; 32]>
) -> Self::SeededRng {
let mut transcript = DigestTranscript::<D>(self.0.clone(), PhantomData); let mut transcript = DigestTranscript::<D>(self.0.clone(), PhantomData);
if additional_entropy.is_some() { if additional_entropy.is_some() {
transcript.append_message(b"additional_entropy", &additional_entropy.unwrap()); transcript.append_message(b"additional_entropy", &additional_entropy.unwrap());