Skip to content

Commit

Permalink
added ops for bls g1/g2 and pow
Browse files Browse the repository at this point in the history
  • Loading branch information
cameroncooper authored and arvidn committed Apr 27, 2023
1 parent df40e59 commit dcf2558
Show file tree
Hide file tree
Showing 7 changed files with 664 additions and 47 deletions.
373 changes: 373 additions & 0 deletions src/bls_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
use crate::allocator::{Allocator, NodePtr};
use crate::cost::{check_cost, Cost};
use crate::node::Node;
use crate::number::number_from_u8;
use crate::op_utils::{
arg_count, atom, check_arg_count, int_atom, mod_group_order, new_atom_and_cost,
number_to_scalar,
};
use crate::reduction::EvalErr;
use crate::reduction::Response;
use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve};
use bls12_381::{
multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt,
};

const BLS_G1_SUBTRACT_BASE_COST: Cost = 132332;
const BLS_G1_SUBTRACT_COST_PER_ARG: Cost = 1362553;
const BLS_G1_MULTIPLY_BASE_COST: Cost = 2154347;
const BLS_G1_MULTIPLY_COST_PER_BYTE: Cost = 12;
const BLS_G1_NEGATE_BASE_COST: Cost = 470779;
const BLS_G2_ADD_BASE_COST: Cost = 45440;
const BLS_G2_ADD_COST_PER_ARG: Cost = 5544581;
const BLS_G2_SUBTRACT_BASE_COST: Cost = 146290;
const BLS_G2_SUBTRACT_COST_PER_ARG: Cost = 5495272;
const BLS_G2_MULTIPLY_BASE_COST: Cost = 10078145;
const BLS_G2_MULTIPLY_COST_PER_BYTE: Cost = 12;
const BLS_G2_NEGATE_BASE_COST: Cost = 1881699;
const BLS_GT_ADD_BASE_COST: Cost = 60118;
const BLS_GT_ADD_COST_PER_ARG: Cost = 62655353;
const BLS_GT_SUBTRACT_BASE_COST: Cost = 42927;
const BLS_GT_SUBTRACT_COST_PER_ARG: Cost = 63060911;
const BLS_GT_MULTIPLY_BASE_COST: Cost = 34026598;
const BLS_GT_MULTIPLY_COST_PER_BYTE: Cost = 12;
const BLS_GT_NEGATE_BASE_COST: Cost = 21787950;
const BLS_PAIRING_BASE_COST: Cost = 4999087;
const BLS_PAIRING_COST_PER_ARG: Cost = 4515438;
const BLS_MAP_TO_G1_BASE_COST: Cost = 610907;
const BLS_MAP_TO_G1_COST_PER_BYTE: Cost = 122;
const BLS_MAP_TO_G1_COST_PER_DST_BYTE: Cost = 135;
const BLS_MAP_TO_G2_BASE_COST: Cost = 3380023;
const BLS_MAP_TO_G2_COST_PER_BYTE: Cost = 122;
const BLS_MAP_TO_G2_COST_PER_DST_BYTE: Cost = 135;

// TODO: add unit test
fn g1_atom(node: Node) -> Result<G1Affine, EvalErr> {
let blob = atom(node.clone(), "G1 atom")?;
if blob.len() != 48 {
return node.err(&format!(
"atom is not G1 size, got {}: Length of bytes object not equal to 48",
hex::encode(blob)
));
}

match G1Affine::from_compressed(blob.try_into().expect("G1 slice is not 48 bytes")).into() {
Some(point) => Ok(point),
_ => node.err("atom is not a G1 point"),
}
}

// TODO: add unit test
fn g2_atom(node: Node) -> Result<G2Affine, EvalErr> {
let blob = atom(node.clone(), "G2 atom")?;
if blob.len() != 96 {
return node.err(&format!(
"atom is not G2 size, got {}: Length of bytes object not equal to 96",
hex::encode(blob)
));
}

match G2Affine::from_compressed(blob.try_into().expect("G2 slice is not 96 bytes")).into() {
Some(point) => Ok(point),
_ => node.err("atom is not a G2 point"),
}
}

// TODO: add unit test
fn gt_atom(node: Node) -> Result<Gt, EvalErr> {
let blob = atom(node.clone(), "Gt atom")?;
if blob.len() != 288 {
return node.err(&format!(
"atom is not Gt size, got {}: Length of bytes object not equal to 288",
hex::encode(blob)
));
}

match Gt::from_compressed(blob.try_into().expect("Gt slice is not 288 bytes")).into() {
Some(point) => Ok(point),
_ => node.err(&format!("atom is not a Gt point {}", hex::encode(blob))),
}
}

pub fn op_bls_g1_subtract(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
let mut cost = BLS_G1_SUBTRACT_BASE_COST;
check_cost(a, cost, max_cost)?;
let mut total: G1Projective = G1Projective::identity();
let mut is_first = true;
for arg in &args {
let point = g1_atom(arg)?;
cost += BLS_G1_SUBTRACT_COST_PER_ARG;
check_cost(a, cost, max_cost)?;
if is_first {
total = G1Projective::from(point);
} else {
total -= point;
};
is_first = false;
}
let total: G1Affine = total.into();
new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_g1_multiply(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
check_arg_count(&args, 2, "bls_g1_multiply")?;

let mut cost = BLS_G1_MULTIPLY_BASE_COST;
check_cost(a, cost, max_cost)?;

let mut total = G1Projective::from(g1_atom(args.first()?)?);
let args = args.rest()?;
let scalar_buf = int_atom(args.first()?, "bls_g1_multiply")?;
cost += scalar_buf.len() as Cost * BLS_G1_MULTIPLY_COST_PER_BYTE;
check_cost(a, cost, max_cost)?;

total *= number_to_scalar(mod_group_order(number_from_u8(scalar_buf)));

let total: G1Affine = total.into();
new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_g1_negate(a: &mut Allocator, input: NodePtr, _max_cost: Cost) -> Response {
let args = Node::new(a, input);
check_arg_count(&args, 1, "bls_g1_negate")?;
let point = g1_atom(args.first()?)?;
let total = -point;
new_atom_and_cost(a, BLS_G1_NEGATE_BASE_COST, &total.to_compressed())
}

pub fn op_bls_g2_add(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
let mut cost = BLS_G2_ADD_BASE_COST;
let mut total: G2Projective = G2Projective::identity();
for arg in &args {
let point = g2_atom(arg)?;
cost += BLS_G2_ADD_COST_PER_ARG;
check_cost(a, cost, max_cost)?;
total += &point;
}
let total: G2Affine = total.into();
new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_g2_subtract(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
let mut cost = BLS_G2_SUBTRACT_BASE_COST;
let mut total: G2Projective = G2Projective::identity();
let mut is_first = true;
for arg in &args {
let point = g2_atom(arg)?;
cost += BLS_G2_SUBTRACT_COST_PER_ARG;
check_cost(a, cost, max_cost)?;
if is_first {
total = G2Projective::from(point);
} else {
total -= point;
};
is_first = false;
}
let total: G2Affine = total.into();
new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_g2_multiply(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
check_arg_count(&args, 2, "op_bls_g2_multiply")?;

let mut cost = BLS_G2_MULTIPLY_BASE_COST;
check_cost(a, cost, max_cost)?;

let mut total = G2Projective::from(g2_atom(args.first()?)?);
let args = args.rest()?;
let scalar_buf = int_atom(args.first()?, "bls_g2_multiply")?;
cost += scalar_buf.len() as Cost * BLS_G2_MULTIPLY_COST_PER_BYTE;
check_cost(a, cost, max_cost)?;

total *= number_to_scalar(mod_group_order(number_from_u8(scalar_buf)));

let total: G2Affine = total.into();
new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_g2_negate(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
check_arg_count(&args, 1, "bls_g2_negate")?;
let cost = BLS_G2_NEGATE_BASE_COST;
check_cost(a, cost, max_cost)?;
let point = g2_atom(args.first()?)?;
let total = -point;
new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_map_to_g1(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
let ac = arg_count(&args, 2);
if !(1..=2).contains(&ac) {
return args.err("bls_map_to_g1 takes exactly 1 or 2 arguments");
}
let mut cost: Cost = BLS_MAP_TO_G1_BASE_COST;

let msg = atom(args.first()?, "bls_map_to_g1")?;
let args = args.rest()?;
cost += msg.len() as Cost * BLS_MAP_TO_G1_COST_PER_BYTE;
check_cost(a, cost, max_cost)?;

let dst: &[u8] = if ac == 2 {
atom(args.first()?, "bls_map_to_g1")?
} else {
b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"
};

cost += dst.len() as Cost * BLS_MAP_TO_G1_COST_PER_DST_BYTE;
check_cost(a, cost, max_cost)?;

let point = <G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, dst);
new_atom_and_cost(a, cost, &G1Affine::from(point).to_compressed())
}

pub fn op_bls_map_to_g2(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
let ac = arg_count(&args, 2);
if !(1..=2).contains(&ac) {
return args.err("bls_map_to_g2 takes exactly 1 or 2 arguments");
}
let mut cost: Cost = BLS_MAP_TO_G2_BASE_COST;
check_cost(a, cost, max_cost)?;

let msg = atom(args.first()?, "bls_map_to_g2")?;
let args = args.rest()?;
cost += msg.len() as Cost * BLS_MAP_TO_G2_COST_PER_BYTE;

let dst: &[u8] = if ac == 2 {
atom(args.first()?, "bls_map_to_g2")?
} else {
b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"
};

cost += dst.len() as Cost * BLS_MAP_TO_G2_COST_PER_DST_BYTE;
check_cost(a, cost, max_cost)?;

let point = <G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, dst);
new_atom_and_cost(a, cost, &G2Affine::from(point).to_compressed())
}

pub fn op_bls_gt_add(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
let mut cost = BLS_GT_ADD_BASE_COST;
let mut total: Gt = Gt::identity();
for arg in &args {
let point = gt_atom(arg)?;
cost += BLS_GT_ADD_COST_PER_ARG;
check_cost(a, cost, max_cost)?;
total += &point;
}
new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_gt_subtract(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
let mut cost = BLS_GT_SUBTRACT_BASE_COST;
let mut total: Gt = Gt::identity();
let mut is_first = true;
for arg in &args {
let point = gt_atom(arg)?;
cost += BLS_GT_SUBTRACT_COST_PER_ARG;
check_cost(a, cost, max_cost)?;
if is_first {
total = point;
} else {
total -= point;
};
is_first = false;
}
new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_gt_multiply(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
check_arg_count(&args, 2, "op_bls_gt_multiply")?;
let mut cost = BLS_GT_MULTIPLY_BASE_COST;
check_cost(a, cost, max_cost)?;

let mut total = Gt::from(gt_atom(args.first()?)?);
let args = args.rest()?;

let scalar_buf = int_atom(args.first()?, "bls_gt_multiply")?;
cost += scalar_buf.len() as Cost * BLS_GT_MULTIPLY_COST_PER_BYTE;
check_cost(a, cost, max_cost)?;

total *= number_to_scalar(mod_group_order(number_from_u8(scalar_buf)));

new_atom_and_cost(a, cost, &total.to_compressed())
}

pub fn op_bls_gt_negate(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
check_arg_count(&args, 1, "bls_gt_negate")?;
let cost = BLS_GT_NEGATE_BASE_COST;
check_cost(a, cost, max_cost)?;
let point = gt_atom(args.first()?)?;
let total = -point;
new_atom_and_cost(a, cost, &total.to_compressed())
}

// TODO: add unit test
fn extract_point(points: Node) -> Result<(G1Affine, G2Prepared), EvalErr> {
check_arg_count(&points, 2, "pairing")?;
let p = g1_atom(points.first()?)?;
let points = points.rest()?;
let q = g2_atom(points.first()?)?;
Ok((p, G2Prepared::from(q)))
}

// TODO: add unit test
fn extract_points(
mut args: Node,
max_cost: u64,
) -> Result<(Vec<(G1Affine, G2Prepared)>, u64), EvalErr> {
let mut cost = 0;
let mut items = Vec::<(G1Affine, G2Prepared)>::new();

while !args.nullp() {
cost += BLS_PAIRING_COST_PER_ARG;
check_cost(&Allocator::new(), cost, max_cost)?;
items.push(extract_point(args.first()?)?);
args = args.rest()?;
}

if items.is_empty() {
return args.err("bls_pairing expects a non-empty list of pairs");
}

Ok((items, cost))
}

// This function accepts either two parameters, G1 and G2 and treats them as a single item
// or a (single) list of pairs (G1Point G2Point)
pub fn op_bls_pairing(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response {
let args = Node::new(a, input);
let mut cost = BLS_PAIRING_BASE_COST;
let mut items = Vec::<(G1Affine, G2Prepared)>::new();
let ac = arg_count(&args, 2);

if ac == 1 {
let (points, additional_cost) = extract_points(args.first()?, max_cost - cost)?;
cost += additional_cost;
check_cost(&Allocator::new(), cost, max_cost)?;
items.extend(points);
} else if ac == 2 {
cost += BLS_PAIRING_COST_PER_ARG;
check_cost(a, cost, max_cost)?;
items.push(extract_point(args)?);
} else {
return args.err("bls_pairing takes exactly 1 list of pairs or 2 atoms");
}

let mut item_refs = Vec::<(&G1Affine, &G2Prepared)>::new();
for (p, q) in &items {
item_refs.push((p, q));
}
let total = multi_miller_loop(&item_refs).final_exponentiation();
new_atom_and_cost(a, cost, &total.to_compressed())
}
Loading

0 comments on commit dcf2558

Please sign in to comment.