Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: consistent pedersen hash (work in progress) #1945

Merged
merged 55 commits into from
Sep 29, 2023
Merged
Changes from 1 commit
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
2acca41
added cycle_group class
zac-williamson Aug 15, 2023
4f885f6
fixed bugs in cycle_group add/sub/dbl
zac-williamson Aug 16, 2023
fbd9b40
variable-base scalar multiplication passes tests
zac-williamson Aug 17, 2023
55f7019
cycle_group::variable_batch_mul now supports input points that are at…
zac-williamson Aug 17, 2023
e339ba0
added an elliptic curve point doubling gate to the UltraPlonk arithme…
zac-williamson Aug 17, 2023
ce45f32
hash to curve
zac-williamson Aug 21, 2023
f5a9a5f
wip
zac-williamson Aug 25, 2023
604ad3f
fixed linting errors in proof_system/plookup_tables
zac-williamson Aug 26, 2023
c09483e
added refactored pedersen hash methods + stdlib::pedersen_hash (needs…
zac-williamson Aug 29, 2023
36624f3
fixed, tidy up, comments
zac-williamson Sep 2, 2023
0608a65
wip
zac-williamson Sep 10, 2023
dcec4f0
Merge remote-tracking branch 'origin/master' into zw/stdlib-cycle-gro…
charlielye Sep 15, 2023
afad6f0
completed merge of master
zac-williamson Sep 15, 2023
4283697
Merge branch 'master' into zw/stdlib-cycle-group-msm
zac-williamson Sep 15, 2023
43086fa
revert formatting
zac-williamson Sep 15, 2023
918410c
removed extra ecc gate methods from standard/turbo circuit builder
zac-williamson Sep 15, 2023
0db1732
wip
zac-williamson Sep 15, 2023
98e5261
PR changes
zac-williamson Sep 15, 2023
af2a6ac
fixed ecc_dbl gates incorrectly fusing into ecc_add gates
zac-williamson Sep 15, 2023
4426370
wip
zac-williamson Sep 15, 2023
fd0de30
compiler fixes
zac-williamson Sep 20, 2023
a29ebb3
compiler fox
zac-williamson Sep 20, 2023
79b17e1
compiler fox
zac-williamson Sep 20, 2023
a0c9f92
compiler fix
zac-williamson Sep 20, 2023
5e7a4d5
compiler fix
zac-williamson Sep 20, 2023
8b58e39
reverted schnorr
zac-williamson Sep 21, 2023
87ff132
compiler fix
zac-williamson Sep 21, 2023
9236506
revert pedersen c_bind
zac-williamson Sep 21, 2023
c899d90
revert crypto/schnorr
zac-williamson Sep 21, 2023
a5254f1
Merge branch 'master' into zw/stdlib-cycle-group-msm
zac-williamson Sep 21, 2023
6c8adcc
bugfix
zac-williamson Sep 22, 2023
0dfc607
Merge branch 'master' into zw/stdlib-cycle-group-msm
zac-williamson Sep 22, 2023
f02c8ae
Yeet.
zac-williamson Sep 28, 2023
6d7553e
remove oof
zac-williamson Sep 28, 2023
e96faf1
comment fixes
zac-williamson Sep 28, 2023
5659240
comments
zac-williamson Sep 28, 2023
730e7ee
fix
zac-williamson Sep 28, 2023
0c7e955
fix
zac-williamson Sep 28, 2023
f782338
fix
zac-williamson Sep 28, 2023
3db1576
comments
zac-williamson Sep 28, 2023
664c869
fix
zac-williamson Sep 28, 2023
be67530
fix
zac-williamson Sep 28, 2023
9563341
fix
zac-williamson Sep 28, 2023
c0ce229
fix
zac-williamson Sep 28, 2023
e672785
fix
zac-williamson Sep 28, 2023
246bf4f
more fix
zac-williamson Sep 28, 2023
d7c120e
names are hard
zac-williamson Sep 28, 2023
c146851
fix
zac-williamson Sep 28, 2023
de6d232
typo
zac-williamson Sep 28, 2023
a04b4b4
test fix
zac-williamson Sep 29, 2023
031c108
Merge branch 'master' into zw/stdlib-cycle-group-msm
zac-williamson Sep 29, 2023
b79cac4
fix
zac-williamson Sep 29, 2023
20bf104
merge fix
zac-williamson Sep 29, 2023
760aba7
comments
zac-williamson Sep 29, 2023
f1b3271
bugfix
zac-williamson Sep 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
reverted schnorr
zac-williamson committed Sep 21, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 8b58e39d0faad284c28a52700d72cd56374c4616
297 changes: 271 additions & 26 deletions barretenberg/cpp/src/barretenberg/stdlib/encryption/schnorr/schnorr.cpp
Original file line number Diff line number Diff line change
@@ -1,62 +1,303 @@
#include "schnorr.hpp"
#include "barretenberg/crypto/pedersen_commitment/pedersen.hpp"
#include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp"
#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp"
#include "barretenberg/stdlib/hash/blake2s/blake2s.hpp"
#include "barretenberg/stdlib/hash/pedersen/pedersen_refactor.hpp"
#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp"
#include "barretenberg/stdlib/primitives/group/cycle_group.hpp"
#include <array>

namespace proof_system::plonk::stdlib::schnorr {
namespace proof_system::plonk {
namespace stdlib {
namespace schnorr {

/**
* @brief Expand a 128-bits integer in a form amenable to doing elliptic curve arithmetic in circuits.
*
* @details The output wnaf_record records the expansion coefficients
* limb % 129 = 2^128 + 2^127 w_1 + ... + 2 w_127 + w_128 - skew
* where each w_i lies in {-1, 1} and skew is 0 or 1. The boolean `skew` could also be called `is_even`; the even
* 129-bit non-negative integers are those with skew == 1, while the odd ones have skew==0.
*
* @warning While it is possible to express any 129-bit value in this form, this function only works correctly
* on 128-bit values, since the same is true for fixed_wnaf<129, 1, 1>. This is illusrated in the tests.
*
*
* TurboPLONK: ~260 gates.
*/
template <typename C> wnaf_record<C> convert_field_into_wnaf(C* context, const field_t<C>& limb)
{
constexpr size_t num_wnaf_bits = 129;
uint256_t value = limb.get_value();

bool skew = false;
uint64_t wnaf_entries[129] = { 0 };

// compute wnaf representation of value natively
barretenberg::wnaf::fixed_wnaf<num_wnaf_bits, 1, 1>(&value.data[0], &wnaf_entries[0], skew, 0);

std::vector<bool_t<C>> wnaf_bits;
bool_t<C> wnaf_skew(witness_t<C>(context, skew));
field_t<C> two(context, 2);
field_t<C> one(context, 1);
field_t<C> accumulator(context, 1);

// set accumulator = 2^{128} + \sum_{i=0}^{127} 2^i w_{128-i}, where w_i = 2 * wnaf_entries[i+1] - 1
for (size_t i = 0; i < 128; ++i) {
// accumulator = 2 * accumulator + 1 (resp. -1) if the 32nd bit of wnaf_entries[i+1] is 0 (resp. 1).

// extract sign bit of wnaf_entries[i+1] (32nd entry in list of bits)
uint64_t predicate = (wnaf_entries[i + 1] >> 31U) & 1U;
// type of !predicate below is bool
bool_t<C> wnaf_bit = witness_t<C>(context, !predicate);
wnaf_bits.push_back(wnaf_bit);

// !predicate == false ~> -1; true ~> +1
accumulator = accumulator + accumulator;
accumulator = accumulator + (field_t<C>(wnaf_bit) * two - one);
}

// subtract 1 from accumulator if there is skew
accumulator = accumulator - field_t<C>(wnaf_skew);

accumulator.assert_equal(limb);
wnaf_record<C> result;
result.bits = wnaf_bits;
result.skew = wnaf_skew;
return result;
}

/**
* @brief Instantiate a witness containing the signature (s, e) as a quadruple of
* field_t elements (s_lo, s_hi, e_lo, e_hi).
*/
template <typename C> signature_bits<C> convert_signature(C* context, const crypto::schnorr::signature& signature)
{
using cycle_scalar = typename cycle_group<C>::cycle_scalar;
signature_bits<C> sig{
field_t<C>(),
field_t<C>(),
field_t<C>(),
field_t<C>(),
};

uint256_t s_bigint(0);
uint256_t e_bigint(0);
const uint8_t* s_ptr = &signature.s[0];
const uint8_t* e_ptr = &signature.e[0];
numeric::read(s_ptr, s_bigint);
numeric::read(e_ptr, e_bigint);
signature_bits<C> sig{ .s = cycle_scalar::from_witness_bitstring(context, s_bigint, 256),
.e = cycle_scalar::from_witness_bitstring(context, e_bigint, 256) };

for (size_t i = 0; i < 32; ++i) {
for (size_t j = 7; j < 8; --j) {
uint8_t s_shift = static_cast<uint8_t>(signature.s[i] >> j);
uint8_t e_shift = static_cast<uint8_t>(signature.e[i] >> j);
bool s_bit = (s_shift & 1U) == 1U;
bool e_bit = (e_shift & 1U) == 1U;
s_bigint += s_bigint;
e_bigint += e_bigint;

s_bigint += static_cast<uint64_t>(s_bit);
e_bigint += static_cast<uint64_t>(e_bit);
}
}

sig.s_lo = witness_t<C>(context, s_bigint.slice(0, 128));
sig.s_hi = witness_t<C>(context, s_bigint.slice(128, 256));
sig.e_lo = witness_t<C>(context, e_bigint.slice(0, 128));
sig.e_hi = witness_t<C>(context, e_bigint.slice(128, 256));

return sig;
}

/**
* @brief Compute [(low_bits + 2^128 high_bits)]pub_key.
*
* @details This method cannot handle the case where either of low_bits, high_bits is zero.
* This assumption is backed by a constraint (see the tests for an illustration).
*/
template <typename C>
point<C> variable_base_mul(const point<C>& pub_key, const field_t<C>& low_bits, const field_t<C>& high_bits)
{
C* context = pub_key.x.context;

// N.B. this method does not currently work if low_bits == 0 or high_bits == 0
field_t<C> zero_test = (low_bits * high_bits);
zero_test.assert_is_not_zero();

const auto low_wnaf = stdlib::schnorr::convert_field_into_wnaf(context, low_bits);
const auto high_wnaf = stdlib::schnorr::convert_field_into_wnaf(context, high_bits);
// current_accumulator is pub_key, so init is true, so high_output is [high_wnaf]pub_key
point<C> high_output = stdlib::schnorr::variable_base_mul(pub_key, pub_key, high_wnaf);
// compute output = [low_wnaf]pub_key + [2^128]high_output.
point<C> output = stdlib::schnorr::variable_base_mul(pub_key, high_output, low_wnaf);
return output;
}

/**
* @brief Multiply a point of Grumpkin by a scalar described as a wnaf record, possibly offsetting by another point.
*
* @param pub_key A point of Grumpkin known to the prover in terms of the generator grumpkin::g1::one.
* @param current_accumulator A point of the curve that will remain unchanged.
* @param wnaf A wnaf_record<C>, a collection of bool_t<C>'s typically recording an expansion of an element of
* field_t<C> in the form 2^{128} + 2^{127} w_1 + ... + 2 w_127 + w_128 - skew.
*
* @details Let W be the scalar represented by wnaf. If pub_key = ± current_accumulator, this function returns
* [W]pub_key. Otherwise, it returns [W]pub_key + [2^128]current_accumulator. These two cases are distinguished
* between a boolean `init`. The idea here is that, if `pub_key==±current_accumulator`, then the function is being
* called for the first time.
*
* @warning This function should not be used on its own, as its security depends on the manner in which it is
* expected to be used.
*/
template <typename C>
point<C> variable_base_mul(const point<C>& pub_key, const point<C>& current_accumulator, const wnaf_record<C>& wnaf)
{
// Check if the pub_key is a points on the curve.
pub_key.on_curve();

// The account circuit constrains `pub_key` to lie on Grumpkin. Presently, the only values that are passed in the
// second argument as `current_accumulator` are `pub_key` and a point which is the output of the present function.
// We therefore assume that `current_accumulator` lies on Grumpkin as well.
grumpkin::g1::affine_element pub_key_native(pub_key.x.get_value(), pub_key.y.get_value());
grumpkin::g1::affine_element current_accumulator_native(current_accumulator.x.get_value(),
current_accumulator.y.get_value());

field_t<C> two(pub_key.x.context, 2);

// Various elliptic curve point additions that follow assume that the two points are distinct and not mutually
// inverse. collision_offset is chosen to prevent a malicious prover from exploiting this assumption.
grumpkin::g1::affine_element collision_offset = crypto::generators::get_generator_data(DEFAULT_GEN_1).generator;
grumpkin::g1::affine_element collision_end = collision_offset * grumpkin::fr(uint256_t(1) << 129);

const bool init = current_accumulator.x.get_value() == pub_key.x.get_value();

// if init == true, check pub_key != collision_offset (ruling out 3 other points at the same time),
// if init == false we assume this has already been checked in an earlier call wherein init==true.
if (init) {
field_t<C> zero_test = ((pub_key.x - collision_offset.x) * (pub_key.y - collision_offset.y));
zero_test.assert_is_not_zero("pub_key and collision_offset have a coordinate in common.");
} else {
// Check if the current_accumulator is a point on the curve only if init is false.
current_accumulator.on_curve();
}

point<C> accumulator{ collision_offset.x, collision_offset.y };

/*
* Let w_i = 2 wnaf.bits[i-1] - 1 for i = 1, ..., 128.
* The integer represented by the digits w_i and a skew bit `skew` in {0, 1} is
* W := 2^{128} + 2^{127} w_1 + ... + 2 w_127 + w_128 - skew
* = 2^{128} + \sum_{k=0}^{127}2^{k}w_{128-k} - skew.
* When init == true, the for loop that follows sets
* accumulator = [W+skew]pub_key + [2^{129}]collision_offset
* When init == false, the for loop that follows sets
* accumulator = [W+skew]pub_key + [2^{129}]collision_offset + [2^{128}]current_accumulator.
* We describe the accumulation process in the loop.
*
* Defining w_{-1} = 0, W_{0} = 1, and W_{i+1} = 2 W_{i} + w_i for i = 1, ..., 128, we have
* W_1 = 2 + w_0
* W_2 = 4 + 2 w_0 + w_1
* W_i = 2^i + 2^{i-1} w_0 + ... + 2 w_{i-2} + w_{i-1}
* W_128 = W + skew
*
* Let A_0 = collision_offset. For i = 0, ..., 127, let
* A_{i+1} = 2^{i+1} collision_offset + [W_{i}]pub_key and A'_{i+1} = A_{i+1} + [2^{i}]current_accumulator.
* Suppose we are at the end of the loop with loop variable i.
* - If `init==true`, then the value of `accumulator` is A_{i+i}.
* - If `init==false`, then the value of `accumulator` is A'_{i+1}.
* In both cases, setting the final accumulator value is that claimed above.
*
* Note that all divisons are safe, i.e., failing contsraints will be imposed if any denominator is zero.
*/
for (size_t i = 0; i < 129; ++i) {
if (!init && i == 1) {
// set accumulator = accumulator + current_accumulator.
field_t<C> x1 = accumulator.x;
field_t<C> y1 = accumulator.y;

field_t<C> x2 = current_accumulator.x;
field_t<C> y2 = current_accumulator.y;

field_t<C> lambda1 = (y2 - y1) / (x2 - x1);
field_t<C> x3 = lambda1.madd(lambda1, -(x2 + x1));
field_t<C> y3 = lambda1.madd((x1 - x3), -y1);
accumulator.x = x3;
accumulator.y = y3;
}

// if i == 0: set accumulator = [2]accumulator + pub_key
// otherwise, set accumulator = [2]accumulator + [w_i]pub_key.

// // Set P_3 = accumulator + pub_key or P_3 = accumulator - pub_key, depending on the current wnaf bit.

field_t<C> x1 = accumulator.x;
field_t<C> y1 = accumulator.y;

field_t<C> x2 = (i == 0) ? pub_key.x : pub_key.x;
field_t<C> y2 = (i == 0) ? pub_key.y : pub_key.y.madd(field_t<C>(wnaf.bits[i - 1]) * two, -pub_key.y);
field_t<C> lambda1 = (y2 - y1) / (x2 - x1);
field_t<C> x3 = lambda1.madd(lambda1, -(x2 + x1));

// // Set P_4 = P_3 + accumulator.
// // We save gates by not using the formula lambda2 = (y3 - y1) / (x3 - x1), which would require computing
// // y_3. Instead we use another formula for lambda2 derived using the substitution y3 = lambda1(x1 - x3) - y1.
field_t<C> lambda2 = -lambda1 - (y1 * two) / (x3 - x1);
field_t<C> x4 = lambda2.madd(lambda2, -(x3 + x1));
field_t<C> y4 = lambda2.madd(x1 - x4, -y1);

accumulator.x = x4;
accumulator.y = y4;
}

// At this point, accumulator is [W + skew]pub + [2^{129}]collision_mask.
// If wnaf_skew, subtract pub_key frorm accumulator.
field_t<C> add_lambda = (accumulator.y + pub_key.y) / (accumulator.x - pub_key.x);
field_t<C> x_add = add_lambda.madd(add_lambda, -(accumulator.x + pub_key.x));
field_t<C> y_add = add_lambda.madd((pub_key.x - x_add), pub_key.y);
bool_t<C> add_predicate = wnaf.skew;
accumulator.x = ((x_add - accumulator.x).madd(field_t<C>(add_predicate), accumulator.x));
accumulator.y = ((y_add - accumulator.y).madd(field_t<C>(add_predicate), accumulator.y));

// subtract [2^{129}]collision_offset from accumulator.
point<C> collision_mask{ collision_end.x, -collision_end.y };

field_t<C> lambda = (accumulator.y - collision_mask.y) / (accumulator.x - collision_mask.x);
field_t<C> x3 = lambda.madd(lambda, -(collision_mask.x + accumulator.x));
field_t<C> y3 = lambda.madd(collision_mask.x - x3, -collision_mask.y);

accumulator.x = x3;
accumulator.y = y3;
return accumulator;
}

/**
* @brief Make the computations needed to verify a signature (s, e), i.e., compute
* e' = hash(([s]g + [e]pub).x | message)
and return e'.
*
* @details UltraPlonk: ~5018 gates, excluding gates required to init the UltraPlonk range check
* (~1,169k for fixed/variable_base_mul, ~4k for blake2s) for a string of length = 34.
* @details TurboPlonk: ~10850 gates (~4k for variable_base_mul, ~6k for blake2s) for a string of length < 32.
*/
template <typename C>
std::array<field_t<C>, 2> verify_signature_internal(const byte_array<C>& message,
const point<C>& pub_key,
const signature_bits<C>& sig)
{
cycle_group<C> key(pub_key.x, pub_key.y, false);
cycle_group<C> g1(grumpkin::g1::one);
// compute g1 * sig.s + key * sig,e
// Compute [s]g, where s = (s_lo, s_hi) and g = G1::one.
point<C> R_1 = group<C>::fixed_base_scalar_mul(sig.s_lo, sig.s_hi);
// Compute [e]pub, where e = (e_lo, e_hi)
point<C> R_2 = variable_base_mul(pub_key, sig.e_lo, sig.e_hi);

// check R_1 != R_2
(R_1.x - R_2.x).assert_is_not_zero("Cannot add points in Schnorr verification.");
// Compute x-coord of R_1 + R_2 = [s]g + [e]pub.
field_t<C> lambda = (R_1.y - R_2.y) / (R_1.x - R_2.x);
field_t<C> x_3 = lambda * lambda - (R_1.x + R_2.x);

auto x_3 = cycle_group<C>::batch_mul({ sig.s, sig.e }, { g1, key }).x;
// build input (pedersen(([s]g + [e]pub).x | pub.x | pub.y) | message) to hash function
// pedersen hash ([r].x | pub.x) to make sure the size of `hash_input` is <= 64 bytes for a 32 byte message
byte_array<C> hash_input(stdlib::pedersen_hash_refactor<C>::hash({ x_3, key.x, key.y }));
byte_array<C> hash_input(stdlib::pedersen_commitment<C>::compress({ x_3, pub_key.x, pub_key.y }));
hash_input.write(message);

// compute e' = hash(([s]g + [e]pub).x | message)
byte_array<C> output = blake2s(hash_input);
static constexpr size_t LO_BYTES = cycle_group<C>::cycle_scalar::LO_BITS / 8;
static constexpr size_t HI_BYTES = 32 - LO_BYTES;
field_t<C> output_hi(output.slice(0, LO_BYTES));
field_t<C> output_lo(output.slice(LO_BYTES, HI_BYTES));

field_t<C> output_hi(output.slice(0, 16));
field_t<C> output_lo(output.slice(16, 16));

return { output_lo, output_hi };
}

@@ -70,8 +311,8 @@ template <typename C>
void verify_signature(const byte_array<C>& message, const point<C>& pub_key, const signature_bits<C>& sig)
{
auto [output_lo, output_hi] = verify_signature_internal(message, pub_key, sig);
output_lo.assert_equal(sig.e.lo, "verify signature failed");
output_hi.assert_equal(sig.e.hi, "verify signature failed");
output_lo.assert_equal(sig.e_lo, "verify signature failed");
output_hi.assert_equal(sig.e_hi, "verify signature failed");
}

/**
@@ -85,12 +326,16 @@ bool_t<C> signature_verification_result(const byte_array<C>& message,
const signature_bits<C>& sig)
{
auto [output_lo, output_hi] = verify_signature_internal(message, pub_key, sig);
bool_t<C> valid = (output_lo == sig.e.lo) && (output_hi == sig.e.hi);
bool_t<C> valid = (output_lo == sig.e_lo) && (output_hi == sig.e_hi);
return valid;
}

INSTANTIATE_STDLIB_METHOD(VARIABLE_BASE_MUL)
INSTANTIATE_STDLIB_METHOD(CONVERT_FIELD_INTO_WNAF)
INSTANTIATE_STDLIB_METHOD(VERIFY_SIGNATURE_INTERNAL)
INSTANTIATE_STDLIB_METHOD(VERIFY_SIGNATURE)
INSTANTIATE_STDLIB_METHOD(SIGNATURE_VERIFICATION_RESULT)
INSTANTIATE_STDLIB_METHOD(CONVERT_SIGNATURE)
} // namespace proof_system::plonk::stdlib::schnorr
} // namespace schnorr
} // namespace stdlib
} // namespace proof_system::plonk
Original file line number Diff line number Diff line change
@@ -6,17 +6,30 @@
#include "../../primitives/point/point.hpp"
#include "../../primitives/witness/witness.hpp"
#include "barretenberg/crypto/schnorr/schnorr.hpp"
#include "barretenberg/stdlib/primitives/group/cycle_group.hpp"

namespace proof_system::plonk {
namespace stdlib {
namespace schnorr {

template <typename C> struct signature_bits {
typename cycle_group<C>::cycle_scalar s;
typename cycle_group<C>::cycle_scalar e;
field_t<C> s_lo;
field_t<C> s_hi;
field_t<C> e_lo;
field_t<C> e_hi;
};

template <typename C> struct wnaf_record {
std::vector<bool_t<C>> bits;
bool_t<C> skew;
};

template <typename C> wnaf_record<C> convert_field_into_wnaf(C* context, const field_t<C>& limb);

template <typename C>
point<C> variable_base_mul(const point<C>& pub_key, const point<C>& current_accumulator, const wnaf_record<C>& scalar);
template <typename C>
point<C> variable_base_mul(const point<C>& pub_key, const field_t<C>& low_bits, const field_t<C>& high_bits);

template <typename C> signature_bits<C> convert_signature(C* context, const crypto::schnorr::signature& sig);

template <typename C>
@@ -55,6 +68,8 @@ bool_t<C> signature_verification_result(const byte_array<C>& message,
#define CONVERT_SIGNATURE(circuit_type) \
signature_bits<circuit_type> convert_signature<circuit_type>(circuit_type*, const crypto::schnorr::signature&)

EXTERN_STDLIB_METHOD(VARIABLE_BASE_MUL)
EXTERN_STDLIB_METHOD(CONVERT_FIELD_INTO_WNAF)
EXTERN_STDLIB_METHOD(VERIFY_SIGNATURE_INTERNAL)
EXTERN_STDLIB_METHOD(VERIFY_SIGNATURE)
EXTERN_STDLIB_METHOD(SIGNATURE_VERIFICATION_RESULT)
Original file line number Diff line number Diff line change
@@ -19,6 +19,163 @@ using field_ct = field_t<Composer>;
using point_ct = point<Composer>;
using witness_ct = witness_t<Composer>;

auto run_scalar_mul_test = [](grumpkin::fr scalar_mont, bool expect_verify) {
Composer composer = Composer();

grumpkin::fr scalar = scalar_mont.from_montgomery_form();

uint256_t scalar_low{ scalar.data[0], scalar.data[1], 0ULL, 0ULL };
uint256_t scalar_high{ scalar.data[2], scalar.data[3], 0ULL, 0ULL };

field_ct input_lo = witness_ct(&composer, scalar_low);
field_ct input_hi = witness_ct(&composer, scalar_high);

grumpkin::g1::element expected = grumpkin::g1::one * scalar_mont;
expected = expected.normalize();
point_ct point_input{ witness_ct(&composer, grumpkin::g1::affine_one.x),
witness_ct(&composer, grumpkin::g1::affine_one.y) };

point_ct output = variable_base_mul(point_input, input_lo, input_hi);

if (expect_verify) {
EXPECT_EQ(output.x.get_value(), expected.x);
EXPECT_EQ(output.y.get_value(), expected.y);
};

info("composer gates = ", composer.get_num_gates());

bool result = composer.check_circuit();
EXPECT_EQ(result, expect_verify);
};

typedef wnaf_record<Composer> wnaf_record_ct;

/**
* @brief Helper function to compare wnaf_records, useful since == on bool_ct's returns a bool_ct.
*/
bool compare_records(wnaf_record_ct a, wnaf_record_ct b)
{
bool result = a.skew.witness_bool == b.skew.witness_bool;
if (result) {
for (size_t i = 0; i != a.bits.size(); ++i) {
bool a_bit = a.bits[i].witness_bool;
bool b_bit = b.bits[i].witness_bool;
result = result == false ? false : a_bit == b_bit;
}
}
return result;
}

TEST(stdlib_schnorr, convert_field_into_wnaf_special)
{
Composer composer = Composer();

// the wnaf_record ((b_1, ... b_128), skew) corresponding to the 129-bit non-negative value
// is, 2^128 + 2^127 w_1 + ... + 2 w_127 + w_128 - skew, where w_i = 1 if b_i is true, else -1..
// We make some auxiliary wnaf records that will be helpful.
std::vector<bool_ct> false128(128, false);
wnaf_record_ct all_false({ .bits = false128, .skew = false });

std::vector<bool_ct> true128(128, true);
wnaf_record_ct all_true({ .bits = true128, .skew = true });

// establish a list of special values to be converted to a wnaf_record
std::vector<uint256_t> special_values({ 1,
0,
(static_cast<uint256_t>(1) << 128) - 1,
(static_cast<uint256_t>(1) << 128) + 1,
(static_cast<uint256_t>(1) << 128),
(static_cast<uint256_t>(1) << 129) - 1 });

size_t num_special_values(special_values.size());

// convert these values to field elements
std::vector<field_ct> special_field_elts(num_special_values);
for (size_t i = 0; i != num_special_values; ++i) {
field_ct a(special_values[i]);
special_field_elts[i] = a;
};

// manually build the expected wnaf records
// 1 is given by ((false, ..., false), false)
auto record_1 = all_false;

// 0 is given by ((false, ..., false), true)
auto record_0 = all_false;
record_0.skew = true;

// 2^128 - 1 = 2^128 - 2^127 + (2^127 - 1) - 0 is given by((false, true, ..., true), false)
auto record_128_minus_1 = all_true;
record_128_minus_1.bits[0] = false;
record_128_minus_1.skew = false;

// 2^128 + 1 = 2^128 + (2^127 - (2^127 - 1)) - 0 is given by((true, false, false, ..., false), false)
auto record_128_plus_1 = all_false;
record_128_plus_1.bits[0] = true;

// 2^128 = 2^128 + (2^127 - (2^127 - 1)) - 1 is given by((true, false, false, ..., false), true)
auto record_128 = all_false;
record_128.bits[0] = true;
record_128.skew = true;

// // 2^129-1 = 2^128 + 2^127 + ... + 1 - 0 should be given by ((true, true, ..., true), false).
// Note: fixed_wnaf<129, 1, 1>, used inside of convert_field_into_wnaf, incorrectly computes the the coefficient
// of
// 2^127 in the wnaf representation of to be -1.
auto record_max = all_true;
record_max.skew = false;

std::vector<wnaf_record_ct> expected_wnaf_records(
{ record_1, record_0, record_128_minus_1, record_128_plus_1, record_128, record_max });

// integers less than 2^128 are converted correctly
for (size_t i = 0; i != num_special_values; ++i) {
field_ct elt = special_field_elts[i];
wnaf_record_ct record = convert_field_into_wnaf(&composer, elt);
wnaf_record_ct expected_record = expected_wnaf_records[i];
bool records_equal = compare_records(record, expected_record);
ASSERT_TRUE(records_equal);
ASSERT_FALSE(composer.failed());
}
}

TEST(stdlib_schnorr, convert_field_into_wnaf)
{
Composer composer = Composer();

grumpkin::fq scalar_mont = grumpkin::fq::random_element();
grumpkin::fq scalar = scalar_mont.from_montgomery_form();

// our wnaf records only represent 128 bits, so we test by generating a field
// element and then truncating.
scalar.data[2] = 0ULL;
scalar.data[3] = 0ULL;

scalar = scalar.to_montgomery_form();

field_ct input(&composer, scalar);
convert_field_into_wnaf(&composer, input);

info("composer gates = ", composer.get_num_gates());

bool result = composer.check_circuit();
EXPECT_EQ(result, true);
}

/**
* @brief Test variable_base_mul(const point<C>& pub_key,
* const field_t<C>& low_bits,
* const field_t<C>& high_bits)
* by taking a random field Fr element s, computing the corresponding Grumpkin G1 element both natively
* and using the function in question (splitting s into 128-bit halves), then comparing the results.
*/
TEST(stdlib_schnorr, test_scalar_mul_low_high)
{
run_scalar_mul_test(grumpkin::fr::random_element(), true);
run_scalar_mul_test(grumpkin::fr(static_cast<uint256_t>(1) << 128), false);
run_scalar_mul_test(0, false);
}

/**
* @test Test circuit verifying a Schnorr signature generated by \see{crypto::schnorr::verify_signature}.
* We only test: messages signed and verified using Grumpkin and the BLAKE2s hash function. We only test