commit
ec85333e44
|
@ -10,7 +10,7 @@ homepage = "https://github.com/zkcrypto/jubjub"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
name = "jubjub"
|
name = "jubjub"
|
||||||
repository = "https://github.com/zkcrypto/jubjub"
|
repository = "https://github.com/zkcrypto/jubjub"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies.bls12_381]
|
[dependencies.bls12_381]
|
||||||
|
@ -30,5 +30,4 @@ version = "0.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = []
|
||||||
std = []
|
|
||||||
|
|
|
@ -10,10 +10,7 @@ This is a pure Rust implementation of the Jubjub elliptic curve group and its as
|
||||||
* **This implementation has not been reviewed or audited. Use at your own risk.**
|
* **This implementation has not been reviewed or audited. Use at your own risk.**
|
||||||
* This implementation targets Rust `1.36` or later.
|
* This implementation targets Rust `1.36` or later.
|
||||||
* All operations are constant time unless explicitly noted.
|
* All operations are constant time unless explicitly noted.
|
||||||
|
* This implementation does not require the Rust standard library.
|
||||||
## Features
|
|
||||||
|
|
||||||
* `std` (on by default): Enables APIs that leverage the Rust standard library.
|
|
||||||
|
|
||||||
## [Documentation](https://docs.rs/jubjub)
|
## [Documentation](https://docs.rs/jubjub)
|
||||||
|
|
||||||
|
|
10
RELEASES.md
10
RELEASES.md
|
@ -1,3 +1,13 @@
|
||||||
|
# 0.3.0
|
||||||
|
|
||||||
|
This release now depends on the `bls12_381` crate, which exposes the `Fq` field type that we re-export.
|
||||||
|
|
||||||
|
* The `Fq` and `Fr` field types now have better constant function support for various operations and constructors.
|
||||||
|
* We no longer depend on the `byteorder` crate.
|
||||||
|
* We've bumped our `rand_core` dev-dependency up to 0.5.
|
||||||
|
* We've removed the `std` and `nightly` features.
|
||||||
|
* We've bumped our dependency of `subtle` up to `^2.2.1`.
|
||||||
|
|
||||||
# 0.2.0
|
# 0.2.0
|
||||||
|
|
||||||
This release switches to `subtle 2.1` to bring in the `CtOption` type, and also makes a few useful API changes.
|
This release switches to `subtle 2.1` to bring in the `CtOption` type, and also makes a few useful API changes.
|
||||||
|
|
87
src/fr.rs
87
src/fr.rs
|
@ -1,3 +1,6 @@
|
||||||
|
//! This module provides an implementation of the Jubjub scalar field $\mathbb{F}_r$
|
||||||
|
//! where `r = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7`
|
||||||
|
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||||
|
@ -6,7 +9,8 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
|
||||||
|
|
||||||
use crate::util::{adc, mac, sbb};
|
use crate::util::{adc, mac, sbb};
|
||||||
|
|
||||||
/// Represents an element of `GF(r)`.
|
/// Represents an element of the scalar field $\mathbb{F}_r$ of the Jubjub elliptic
|
||||||
|
/// curve construction.
|
||||||
// The internal representation of this type is four 64-bit unsigned
|
// The internal representation of this type is four 64-bit unsigned
|
||||||
// integers in little-endian order. Elements of Fr are always in
|
// integers in little-endian order. Elements of Fr are always in
|
||||||
// Montgomery form; i.e., Fr(a) = aR mod r, with R = 2^256.
|
// Montgomery form; i.e., Fr(a) = aR mod r, with R = 2^256.
|
||||||
|
@ -40,6 +44,7 @@ impl ConstantTimeEq for Fr {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Fr {
|
impl PartialEq for Fr {
|
||||||
|
#[inline]
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.ct_eq(other).unwrap_u8() == 1
|
self.ct_eq(other).unwrap_u8() == 1
|
||||||
}
|
}
|
||||||
|
@ -70,19 +75,7 @@ impl<'a> Neg for &'a Fr {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn neg(self) -> Fr {
|
fn neg(self) -> Fr {
|
||||||
// Subtract `self` from `MODULUS` to negate. Ignore the final
|
self.neg()
|
||||||
// borrow because it cannot underflow; self is guaranteed to
|
|
||||||
// be in the field.
|
|
||||||
let (d0, borrow) = sbb(MODULUS.0[0], self.0[0], 0);
|
|
||||||
let (d1, borrow) = sbb(MODULUS.0[1], self.0[1], borrow);
|
|
||||||
let (d2, borrow) = sbb(MODULUS.0[2], self.0[2], borrow);
|
|
||||||
let (d3, _) = sbb(MODULUS.0[3], self.0[3], borrow);
|
|
||||||
|
|
||||||
// `tmp` could be `MODULUS` if `self` was zero. Create a mask that is
|
|
||||||
// zero if `self` was zero, and `u64::max_value()` if self was nonzero.
|
|
||||||
let mask = u64::from((self.0[0] | self.0[1] | self.0[2] | self.0[3]) == 0).wrapping_sub(1);
|
|
||||||
|
|
||||||
Fr([d0 & mask, d1 & mask, d2 & mask, d3 & mask])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,24 +93,16 @@ impl<'a, 'b> Sub<&'b Fr> for &'a Fr {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sub(self, rhs: &'b Fr) -> Fr {
|
fn sub(self, rhs: &'b Fr) -> Fr {
|
||||||
self.subtract(rhs)
|
self.sub(rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Add<&'b Fr> for &'a Fr {
|
impl<'a, 'b> Add<&'b Fr> for &'a Fr {
|
||||||
type Output = Fr;
|
type Output = Fr;
|
||||||
|
|
||||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn add(self, rhs: &'b Fr) -> Fr {
|
fn add(self, rhs: &'b Fr) -> Fr {
|
||||||
let (d0, carry) = adc(self.0[0], rhs.0[0], 0);
|
self.add(rhs)
|
||||||
let (d1, carry) = adc(self.0[1], rhs.0[1], carry);
|
|
||||||
let (d2, carry) = adc(self.0[2], rhs.0[2], carry);
|
|
||||||
let (d3, _) = adc(self.0[3], rhs.0[3], carry);
|
|
||||||
|
|
||||||
// Attempt to subtract the modulus, to ensure the value
|
|
||||||
// is smaller than the modulus.
|
|
||||||
Fr([d0, d1, d2, d3]) - &MODULUS
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +113,7 @@ impl<'a, 'b> Mul<&'b Fr> for &'a Fr {
|
||||||
fn mul(self, rhs: &'b Fr) -> Fr {
|
fn mul(self, rhs: &'b Fr) -> Fr {
|
||||||
// Schoolbook multiplication
|
// Schoolbook multiplication
|
||||||
|
|
||||||
self.multiply(rhs)
|
self.mul(rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,20 +156,20 @@ impl Default for Fr {
|
||||||
impl Fr {
|
impl Fr {
|
||||||
/// Returns zero, the additive identity.
|
/// Returns zero, the additive identity.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn zero() -> Fr {
|
pub const fn zero() -> Fr {
|
||||||
Fr([0, 0, 0, 0])
|
Fr([0, 0, 0, 0])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns one, the multiplicative identity.
|
/// Returns one, the multiplicative identity.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn one() -> Fr {
|
pub const fn one() -> Fr {
|
||||||
R
|
R
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Doubles this field element.
|
/// Doubles this field element.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn double(&self) -> Fr {
|
pub const fn double(&self) -> Fr {
|
||||||
self + self
|
self.add(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to convert a little-endian byte representation of
|
/// Attempts to convert a little-endian byte representation of
|
||||||
|
@ -268,9 +253,9 @@ impl Fr {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts from an integer represented in little endian
|
/// Converts from an integer represented in little endian
|
||||||
/// into its (congruent) representation in Fr.
|
/// into its (congruent) `Fr` representation.
|
||||||
pub const fn from_raw(val: [u64; 4]) -> Self {
|
pub const fn from_raw(val: [u64; 4]) -> Self {
|
||||||
Fr(val).multiply(&R2)
|
(&Fr(val)).mul(&R2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Squares this element.
|
/// Squares this element.
|
||||||
|
@ -508,11 +493,12 @@ impl Fr {
|
||||||
let (r7, _) = adc(r7, carry2, carry);
|
let (r7, _) = adc(r7, carry2, carry);
|
||||||
|
|
||||||
// Result may be within MODULUS of the correct value
|
// Result may be within MODULUS of the correct value
|
||||||
Fr([r4, r5, r6, r7]).subtract(&MODULUS)
|
(&Fr([r4, r5, r6, r7])).sub(&MODULUS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Multiplies this element by another element
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn multiply(&self, rhs: &Self) -> Self {
|
pub const fn mul(&self, rhs: &Self) -> Self {
|
||||||
// Schoolbook multiplication
|
// Schoolbook multiplication
|
||||||
|
|
||||||
let (r0, carry) = mac(0, self.0[0], rhs.0[0], 0);
|
let (r0, carry) = mac(0, self.0[0], rhs.0[0], 0);
|
||||||
|
@ -538,8 +524,9 @@ impl Fr {
|
||||||
Fr::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7)
|
Fr::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subtracts another element from this element.
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn subtract(&self, rhs: &Self) -> Self {
|
pub const fn sub(&self, rhs: &Self) -> Self {
|
||||||
let (d0, borrow) = sbb(self.0[0], rhs.0[0], 0);
|
let (d0, borrow) = sbb(self.0[0], rhs.0[0], 0);
|
||||||
let (d1, borrow) = sbb(self.0[1], rhs.0[1], borrow);
|
let (d1, borrow) = sbb(self.0[1], rhs.0[1], borrow);
|
||||||
let (d2, borrow) = sbb(self.0[2], rhs.0[2], borrow);
|
let (d2, borrow) = sbb(self.0[2], rhs.0[2], borrow);
|
||||||
|
@ -554,6 +541,37 @@ impl Fr {
|
||||||
|
|
||||||
Fr([d0, d1, d2, d3])
|
Fr([d0, d1, d2, d3])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds this element to another element.
|
||||||
|
#[inline]
|
||||||
|
pub const fn add(&self, rhs: &Self) -> Self {
|
||||||
|
let (d0, carry) = adc(self.0[0], rhs.0[0], 0);
|
||||||
|
let (d1, carry) = adc(self.0[1], rhs.0[1], carry);
|
||||||
|
let (d2, carry) = adc(self.0[2], rhs.0[2], carry);
|
||||||
|
let (d3, _) = adc(self.0[3], rhs.0[3], carry);
|
||||||
|
|
||||||
|
// Attempt to subtract the modulus, to ensure the value
|
||||||
|
// is smaller than the modulus.
|
||||||
|
(&Fr([d0, d1, d2, d3])).sub(&MODULUS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Negates this element.
|
||||||
|
#[inline]
|
||||||
|
pub const fn neg(&self) -> Self {
|
||||||
|
// Subtract `self` from `MODULUS` to negate. Ignore the final
|
||||||
|
// borrow because it cannot underflow; self is guaranteed to
|
||||||
|
// be in the field.
|
||||||
|
let (d0, borrow) = sbb(MODULUS.0[0], self.0[0], 0);
|
||||||
|
let (d1, borrow) = sbb(MODULUS.0[1], self.0[1], borrow);
|
||||||
|
let (d2, borrow) = sbb(MODULUS.0[2], self.0[2], borrow);
|
||||||
|
let (d3, _) = sbb(MODULUS.0[3], self.0[3], borrow);
|
||||||
|
|
||||||
|
// `tmp` could be `MODULUS` if `self` was zero. Create a mask that is
|
||||||
|
// zero if `self` was zero, and `u64::max_value()` if self was nonzero.
|
||||||
|
let mask = (((self.0[0] | self.0[1] | self.0[2] | self.0[3]) == 0) as u64).wrapping_sub(1);
|
||||||
|
|
||||||
|
Fr([d0 & mask, d1 & mask, d2 & mask, d3 & mask])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a Fr> for [u8; 32] {
|
impl<'a> From<&'a Fr> for [u8; 32] {
|
||||||
|
@ -577,7 +595,6 @@ fn test_inv() {
|
||||||
assert_eq!(inv, INV);
|
assert_eq!(inv, INV);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug() {
|
fn test_debug() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -22,8 +22,13 @@
|
||||||
#![deny(missing_debug_implementations)]
|
#![deny(missing_debug_implementations)]
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
// This lint is described at
|
||||||
|
// https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
|
||||||
|
// In our library, some of the arithmetic will necessarily involve various binary
|
||||||
|
// operators, and so this lint is triggered unnecessarily.
|
||||||
|
#![allow(clippy::suspicious_arithmetic_impl)]
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate std;
|
extern crate std;
|
||||||
|
|
||||||
|
@ -968,7 +973,6 @@ fn test_assoc() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_batch_normalize() {
|
fn test_batch_normalize() {
|
||||||
let mut p = ExtendedPoint::from(AffinePoint {
|
let mut p = ExtendedPoint::from(AffinePoint {
|
||||||
|
|
117
src/util.rs
117
src/util.rs
|
@ -19,92 +19,75 @@ pub const fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) {
|
||||||
(ret as u64, (ret >> 64) as u64)
|
(ret as u64, (ret >> 64) as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_binops_additive {
|
macro_rules! impl_add_binop_specify_output {
|
||||||
($lhs:ident, $rhs:ident) => {
|
($lhs:ident, $rhs:ident, $output:ident) => {
|
||||||
impl<'b> Sub<&'b $rhs> for $lhs {
|
|
||||||
type Output = $lhs;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn sub(self, rhs: &'b $rhs) -> $lhs {
|
|
||||||
&self - rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> Add<&'b $rhs> for $lhs {
|
impl<'b> Add<&'b $rhs> for $lhs {
|
||||||
type Output = $lhs;
|
type Output = $output;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn add(self, rhs: &'b $rhs) -> $lhs {
|
fn add(self, rhs: &'b $rhs) -> $output {
|
||||||
&self + rhs
|
&self + rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Sub<$rhs> for &'a $lhs {
|
|
||||||
type Output = $lhs;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn sub(self, rhs: $rhs) -> $lhs {
|
|
||||||
self - &rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Add<$rhs> for &'a $lhs {
|
impl<'a> Add<$rhs> for &'a $lhs {
|
||||||
type Output = $lhs;
|
type Output = $output;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn add(self, rhs: $rhs) -> $lhs {
|
fn add(self, rhs: $rhs) -> $output {
|
||||||
self + &rhs
|
self + &rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sub<$rhs> for $lhs {
|
|
||||||
type Output = $lhs;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn sub(self, rhs: $rhs) -> $lhs {
|
|
||||||
&self - &rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add<$rhs> for $lhs {
|
impl Add<$rhs> for $lhs {
|
||||||
type Output = $lhs;
|
type Output = $output;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn add(self, rhs: $rhs) -> $lhs {
|
fn add(self, rhs: $rhs) -> $output {
|
||||||
&self + &rhs
|
&self + &rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_sub_binop_specify_output {
|
||||||
|
($lhs:ident, $rhs:ident, $output:ident) => {
|
||||||
|
impl<'b> Sub<&'b $rhs> for $lhs {
|
||||||
|
type Output = $output;
|
||||||
|
|
||||||
impl SubAssign<$rhs> for $lhs {
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sub_assign(&mut self, rhs: $rhs) {
|
fn sub(self, rhs: &'b $rhs) -> $output {
|
||||||
*self = &*self - &rhs;
|
&self - rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddAssign<$rhs> for $lhs {
|
impl<'a> Sub<$rhs> for &'a $lhs {
|
||||||
|
type Output = $output;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn add_assign(&mut self, rhs: $rhs) {
|
fn sub(self, rhs: $rhs) -> $output {
|
||||||
*self = &*self + &rhs;
|
self - &rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'b> SubAssign<&'b $rhs> for $lhs {
|
impl Sub<$rhs> for $lhs {
|
||||||
#[inline]
|
type Output = $output;
|
||||||
fn sub_assign(&mut self, rhs: &'b $rhs) {
|
|
||||||
*self = &*self - rhs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> AddAssign<&'b $rhs> for $lhs {
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn add_assign(&mut self, rhs: &'b $rhs) {
|
fn sub(self, rhs: $rhs) -> $output {
|
||||||
*self = &*self + rhs;
|
&self - &rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_binops_additive_specify_output {
|
||||||
|
($lhs:ident, $rhs:ident, $output:ident) => {
|
||||||
|
impl_add_binop_specify_output!($lhs, $rhs, $output);
|
||||||
|
impl_sub_binop_specify_output!($lhs, $rhs, $output);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_binops_multiplicative_mixed {
|
macro_rules! impl_binops_multiplicative_mixed {
|
||||||
($lhs:ident, $rhs:ident, $output:ident) => {
|
($lhs:ident, $rhs:ident, $output:ident) => {
|
||||||
impl<'b> Mul<&'b $rhs> for $lhs {
|
impl<'b> Mul<&'b $rhs> for $lhs {
|
||||||
|
@ -136,6 +119,40 @@ macro_rules! impl_binops_multiplicative_mixed {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_binops_additive {
|
||||||
|
($lhs:ident, $rhs:ident) => {
|
||||||
|
impl_binops_additive_specify_output!($lhs, $rhs, $lhs);
|
||||||
|
|
||||||
|
impl SubAssign<$rhs> for $lhs {
|
||||||
|
#[inline]
|
||||||
|
fn sub_assign(&mut self, rhs: $rhs) {
|
||||||
|
*self = &*self - &rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<$rhs> for $lhs {
|
||||||
|
#[inline]
|
||||||
|
fn add_assign(&mut self, rhs: $rhs) {
|
||||||
|
*self = &*self + &rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> SubAssign<&'b $rhs> for $lhs {
|
||||||
|
#[inline]
|
||||||
|
fn sub_assign(&mut self, rhs: &'b $rhs) {
|
||||||
|
*self = &*self - rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> AddAssign<&'b $rhs> for $lhs {
|
||||||
|
#[inline]
|
||||||
|
fn add_assign(&mut self, rhs: &'b $rhs) {
|
||||||
|
*self = &*self + rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_binops_multiplicative {
|
macro_rules! impl_binops_multiplicative {
|
||||||
($lhs:ident, $rhs:ident) => {
|
($lhs:ident, $rhs:ident) => {
|
||||||
impl_binops_multiplicative_mixed!($lhs, $rhs, $lhs);
|
impl_binops_multiplicative_mixed!($lhs, $rhs, $lhs);
|
||||||
|
|
Loading…
Reference in New Issue