Skip to content

Commit

Permalink
Merge pull request #170 from LLFourn/proc_macro_arithmetic
Browse files Browse the repository at this point in the history
Use procedural macro for `g!` and `s!` macros
  • Loading branch information
LLFourn authored Jan 3, 2024
2 parents 296fd6f + 1bdce07 commit 2467ece
Show file tree
Hide file tree
Showing 32 changed files with 1,163 additions and 479 deletions.
93 changes: 36 additions & 57 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,41 @@ on:
# Make sure CI fails on all warnings, including Clippy lints
env:
RUSTFLAGS: "-Dwarnings"
RUSTDOCFLAGS: "-Dwarnings"

jobs:

fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- run: cargo fmt --all -- --check

clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Clippy
run: cargo clippy --all-targets --all-features
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --all-targets --all-features --tests

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/[email protected]
- uses: Swatinem/rust-cache@v2
- run: |
cargo update -p proptest --precise "1.2.0"
cargo update -p tempfile --precise "3.3.0"
- run: cargo tree --all-features # to debug deps issues
- run: cargo build --release --all-features

# We want to test stable on multiple platforms with --all-features
test:
Expand All @@ -45,11 +54,11 @@ jobs:
matrix:
target: ["x86_64-unknown-linux-gnu", "armv7-unknown-linux-gnueabihf"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.60.0
toolchain: stable
target: ${{ matrix.target }}
override: true
- uses: Swatinem/[email protected]
Expand All @@ -65,18 +74,10 @@ jobs:
test-nightly:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
target: "x86_64-unknown-linux-gnu"
override: true
- uses: Swatinem/[email protected]
- uses: actions-rs/cargo@v1
with:
command: test
args: --release --all-features
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- run: cargo test --release --all-features

# test without default features
test-minimal:
Expand All @@ -85,18 +86,10 @@ jobs:
matrix:
package: [ "secp256kfun", "sigma_fun", "ecdsa_fun", "schnorr_fun" ]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: "x86_64-unknown-linux-gnu"
override: true
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/[email protected]
- uses: actions-rs/cargo@v1
with:
command: test
args: --release --no-default-features -p ${{ matrix.package }}
- run: cargo test --release --no-default-features -p ${{ matrix.package }}


# test with alloc feature only
Expand All @@ -106,30 +99,16 @@ jobs:
matrix:
package: [ "secp256kfun", "sigma_fun", "ecdsa_fun", "schnorr_fun" ]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: "x86_64-unknown-linux-gnu"
override: true
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/[email protected]
- uses: actions-rs/cargo@v1
with:
command: test
args: --release --no-default-features --features alloc -p ${{ matrix.package }}
- run: cargo test --release --no-default-features --features alloc -p ${{ matrix.package }}


doc-build:
name: doc-build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- name: build-doc
# convoluted way to make it fail on warnings
run: "cargo doc --no-deps --workspace 2>&1 | tee /dev/fd/2 | grep -iEq '^(warning|error)' && exit 1 || exit 0"
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo doc --no-deps --workspace
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# CHANGELOG


## Unreleased

- Added `arithmetic_macros` to make `g!` and `s!` macros into procedural macros
- Made even `Secret` things `Copy`. See discussion [here](https://github.com/LLFourn/secp256kfun/issues/6#issuecomment-1363752651).

## v0.9.1

- Added more `bincode` derives for FROST things
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"secp256kfun",
"schnorr_fun",
"ecdsa_fun",
"sigma_fun"
"sigma_fun",
"arithmetic_macros"
]
resolver = "2"
13 changes: 13 additions & 0 deletions arithmetic_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "secp256kfun_arithmetic_macros"
version = "0.9.0"
edition = "2021"

[lib]
proc-macro = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
proc-macro2 = "1"
quote = "1"
168 changes: 168 additions & 0 deletions arithmetic_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
mod optree;
use optree::{Infix, InfixKind, Node, OpTree};
use proc_macro::TokenStream;
use proc_macro2::{Ident, TokenTree};
use quote::{quote, quote_spanned};
use std::iter::Peekable;
type Input = Peekable<proc_macro2::token_stream::IntoIter>;

#[proc_macro]
pub fn gen_s(input: TokenStream) -> TokenStream {
let input: proc_macro2::TokenStream = input.into();
let mut iter = input.into_iter().peekable();

let path = match iter.next() {
Some(TokenTree::Ident(path)) => path,
_ => panic!("put the path to secpfun crate first"),
};
let optree = match optree::parse_tokens(&mut iter) {
Ok(optree) => optree,
Err(e) => {
let problem = e.problem;
return quote_spanned!(e.span => compile_error!(#problem)).into();
}
};

compile_s(&path, optree).into()
}

fn compile_s(path: &Ident, node: Node) -> proc_macro2::TokenStream {
match *node.tree {
OpTree::Infix(Infix { lhs, rhs, kind }) => {
let lhs_ = compile_s(path, lhs);
let mut rhs_ = compile_s(path, rhs);
let fn_name = Ident::new(
match kind {
InfixKind::Add => "scalar_add",
InfixKind::Mul => "scalar_mul",
InfixKind::Sub => "scalar_sub",
InfixKind::LinComb => "scalar_dot_product",
InfixKind::Div => {
rhs_ = quote_spanned! { node.span => #path::op::scalar_invert(#rhs_) };
"scalar_mul"
}
},
node.span,
);

quote_spanned! { node.span => #path::op::#fn_name(#lhs_, #rhs_) }
}
OpTree::Unary(unary) => match unary.kind {
optree::UnaryKind::Neg => {
let fn_name = Ident::new("scalar_negate", node.span);
let subj = compile_s(path, unary.subj);
quote_spanned! { node.span => #path::op::#fn_name(#subj) }
}
optree::UnaryKind::Ref => {
let a = unary.punct;
let subj = compile_g(path, unary.subj);
quote!( #a #subj )
}
},
OpTree::Term(ts) => ts,
OpTree::Paren(node) => compile_s(path, node),
OpTree::LitInt(lit_int) => {
if lit_int == 0 {
quote_spanned! { node.span => #path::Scalar::<#path::marker::Secret, _>::zero() }
} else {
quote_spanned! { node.span =>
#path::Scalar::<#path::marker::Secret, #path::marker::NonZero>::from_non_zero_u32(unsafe {
core::num::NonZeroU32::new_unchecked(#lit_int)
})
}
}
}
}
}

#[proc_macro]
pub fn gen_g(input: TokenStream) -> TokenStream {
let input: proc_macro2::TokenStream = input.into();
let mut iter = input.into_iter().peekable();

let path = match iter.next() {
Some(TokenTree::Ident(path)) => path,
_ => panic!("put the path to secpfun crate first"),
};
let node = match optree::parse_tokens(&mut iter) {
Ok(optree) => optree,
Err(e) => {
let problem = e.problem;
return quote_spanned!(e.span => compile_error!(#problem)).into();
}
};

compile_g(&path, node).into()
}

fn compile_g(path: &Ident, node: Node) -> proc_macro2::TokenStream {
match *node.tree {
OpTree::Infix(Infix { lhs, rhs, kind }) => match kind {
InfixKind::Add | InfixKind::Sub => {
let is_sub = kind == InfixKind::Sub;
match (&*lhs.tree, &*rhs.tree) {
(
OpTree::Infix(Infix {
kind: InfixKind::Mul,
lhs: llhs,
rhs: lrhs,
}),
OpTree::Infix(Infix {
kind: InfixKind::Mul,
lhs: rlhs,
rhs: rrhs,
}),
) => {
let llhs = compile_s(path, llhs.clone());
let lrhs = compile_g(path, lrhs.clone());
let mut rlhs = compile_s(path, rlhs.clone());
let rrhs = compile_g(path, rrhs.clone());
if is_sub {
rlhs = quote_spanned! { node.span => #path::op::scalar_negate(#rlhs) };
}
quote_spanned! { node.span => #path::op::double_mul(#llhs, #lrhs, #rlhs, #rrhs) }
}
(..) => {
let lhs_ = compile_g(path, lhs);
let rhs_ = compile_g(path, rhs);
if is_sub {
quote_spanned! { node.span => #path::op::point_sub(#lhs_, #rhs_) }
} else {
quote_spanned! { node.span => #path::op::point_add(#lhs_, #rhs_) }
}
}
}
}
InfixKind::Mul => {
let lhs_ = compile_s(path, lhs);
let rhs_ = compile_g(path, rhs);
quote_spanned! { node.span => #path::op::scalar_mul_point(#lhs_, #rhs_) }
}
InfixKind::LinComb => {
let lhs_ = compile_s(path, lhs);
let rhs_ = compile_g(path, rhs);
quote_spanned! { node.span => #path::op::point_scalar_dot_product(#lhs_, #rhs_) }
}
InfixKind::Div => {
quote_spanned! { node.span => compile_error!("can't use division in group expression") }
}
},
OpTree::Term(term) => term,
OpTree::Paren(node) => compile_g(path, node),
OpTree::Unary(unary) => match unary.kind {
optree::UnaryKind::Neg => {
let fn_name = Ident::new("point_negate", node.span);
let subj = compile_g(path, unary.subj);
quote_spanned! { node.span => #path::op::#fn_name(#subj) }
}
optree::UnaryKind::Ref => {
let a = unary.punct;
let subj = compile_g(path, unary.subj);
quote!( #a #subj )
}
},
OpTree::LitInt(lit_int) => {
quote_spanned! { node.span => compile_error!("can't use literal int {} in group expression", #lit_int)}
}
}
}
Loading

0 comments on commit 2467ece

Please sign in to comment.