diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index 350392c187..69d867a2f7 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -76,6 +76,7 @@ if(WASM) $ $ $ + $ $ $ $ @@ -91,6 +92,7 @@ if(WASM) $ $ $ + $ $ $ $ @@ -124,6 +126,7 @@ if(WASM) $ $ $ + $ $ $ $ @@ -178,6 +181,7 @@ if(WASM) $ $ $ + $ $ $ $ @@ -193,6 +197,7 @@ if(WASM) $ $ $ + $ $ $ $ @@ -213,6 +218,7 @@ else() $ $ $ + $ $ $ $ @@ -228,6 +234,7 @@ else() $ $ $ + $ $ $ $ diff --git a/cpp/src/barretenberg/crypto/ecdsa/c_bind.cpp b/cpp/src/barretenberg/crypto/ecdsa/c_bind.cpp new file mode 100644 index 0000000000..b7a8d6f5a9 --- /dev/null +++ b/cpp/src/barretenberg/crypto/ecdsa/c_bind.cpp @@ -0,0 +1,70 @@ +#include "ecdsa.hpp" +#include + +#define WASM_EXPORT __attribute__((visibility("default"))) + +extern "C" { + +WASM_EXPORT void ecdsa__compute_public_key(uint8_t const* private_key, uint8_t* public_key_buf) +{ + auto priv_key = from_buffer(private_key); + secp256k1::g1::affine_element pub_key = secp256k1::g1::one * priv_key; + write(public_key_buf, pub_key); +} + +WASM_EXPORT void ecdsa__construct_signature(uint8_t const* message, + size_t msg_len, + uint8_t const* private_key, + uint8_t* output_sig_r, + uint8_t* output_sig_s, + uint8_t* output_sig_v) +{ + using serialize::write; + auto priv_key = from_buffer(private_key); + secp256k1::g1::affine_element pub_key = secp256k1::g1::one * priv_key; + crypto::ecdsa::key_pair key_pair = { priv_key, pub_key }; + + auto sig = crypto::ecdsa::construct_signature( + std::string((char*)message, msg_len), key_pair); + write(output_sig_r, sig.r); + write(output_sig_s, sig.s); + write(output_sig_v, sig.v); +} + +WASM_EXPORT void ecdsa__recover_public_key_from_signature(uint8_t const* message, + size_t msg_len, + uint8_t const* sig_r, + uint8_t const* sig_s, + uint8_t* sig_v, + uint8_t* output_pub_key) +{ + std::array r, s; + std::copy(sig_r, sig_r + 32, r.begin()); + std::copy(sig_s, sig_s + 32, s.begin()); + const uint8_t v = *sig_v; + + crypto::ecdsa::signature sig = { r, s, v }; + auto recovered_pub_key = + crypto::ecdsa::recover_public_key( + std::string((char*)message, msg_len), sig); + write(output_pub_key, recovered_pub_key); +} + +WASM_EXPORT bool ecdsa__verify_signature(uint8_t const* message, + size_t msg_len, + uint8_t const* pub_key, + uint8_t const* sig_r, + uint8_t const* sig_s, + uint8_t const* sig_v) +{ + auto pubk = from_buffer(pub_key); + std::array r, s; + std::copy(sig_r, sig_r + 32, r.begin()); + std::copy(sig_s, sig_s + 32, s.begin()); + const uint8_t v = *sig_v; + + crypto::ecdsa::signature sig = { r, s, v }; + return crypto::ecdsa::verify_signature( + std::string((char*)message, msg_len), pubk, sig); +} +} \ No newline at end of file diff --git a/cpp/src/barretenberg/crypto/ecdsa/c_bind.h b/cpp/src/barretenberg/crypto/ecdsa/c_bind.h new file mode 100644 index 0000000000..897202a372 --- /dev/null +++ b/cpp/src/barretenberg/crypto/ecdsa/c_bind.h @@ -0,0 +1,29 @@ +#include + +#define WASM_EXPORT __attribute__((visibility("default"))) + +extern "C" { + +WASM_EXPORT void ecdsa__compute_public_key(uint8_t const* private_key, uint8_t* public_key_buf); + +WASM_EXPORT void ecdsa__construct_signature(uint8_t const* message, + size_t msg_len, + uint8_t const* private_key, + uint8_t* output_sig_r, + uint8_t* output_sig_s, + uint8_t* output_sig_v); + +WASM_EXPORT void ecdsa__recover_public_key_from_signature(uint8_t const* message, + size_t msg_len, + uint8_t const* sig_r, + uint8_t const* sig_s, + uint8_t* sig_v, + uint8_t* output_pub_key); + +WASM_EXPORT bool ecdsa__verify_signature(uint8_t const* message, + size_t msg_len, + uint8_t const* pub_key, + uint8_t const* sig_r, + uint8_t const* sig_s, + uint8_t const* sig_v); +} diff --git a/cpp/src/barretenberg/crypto/ecdsa/ecdsa.hpp b/cpp/src/barretenberg/crypto/ecdsa/ecdsa.hpp index dea2e18658..57e9074a1b 100644 --- a/cpp/src/barretenberg/crypto/ecdsa/ecdsa.hpp +++ b/cpp/src/barretenberg/crypto/ecdsa/ecdsa.hpp @@ -30,17 +30,18 @@ bool verify_signature(const std::string& message, inline bool operator==(signature const& lhs, signature const& rhs) { - return lhs.r == rhs.r && lhs.s == rhs.s; + return lhs.r == rhs.r && lhs.s == rhs.s && lhs.v == rhs.v; } inline std::ostream& operator<<(std::ostream& os, signature const& sig) { - os << "{ " << sig.r << ", " << sig.s << " }"; + os << "{ " << sig.r << ", " << sig.s << ", " << static_cast(sig.v) << " }"; return os; } template inline void read(B& it, signature& sig) { + using serialize::read; read(it, sig.r); read(it, sig.s); read(it, sig.v); @@ -48,6 +49,7 @@ template inline void read(B& it, signature& sig) template inline void write(B& buf, signature const& sig) { + using serialize::write; write(buf, sig.r); write(buf, sig.s); write(buf, sig.v); diff --git a/cpp/src/barretenberg/crypto/generators/generator_data.cpp b/cpp/src/barretenberg/crypto/generators/generator_data.cpp index e48e21e447..20345bae16 100644 --- a/cpp/src/barretenberg/crypto/generators/generator_data.cpp +++ b/cpp/src/barretenberg/crypto/generators/generator_data.cpp @@ -294,4 +294,4 @@ const fixed_base_ladder* generator_data::get_hash_ladder(size_t num_bits) const } } // namespace generators -} // namespace crypto +} // namespace crypto \ No newline at end of file diff --git a/cpp/src/barretenberg/crypto/pedersen_hash/c_bind.cpp b/cpp/src/barretenberg/crypto/pedersen_hash/c_bind.cpp index fc28d6fff0..ffffc314dc 100644 --- a/cpp/src/barretenberg/crypto/pedersen_hash/c_bind.cpp +++ b/cpp/src/barretenberg/crypto/pedersen_hash/c_bind.cpp @@ -11,6 +11,7 @@ extern "C" { WASM_EXPORT void pedersen_hash__init() { + // TODO: do we need this if we are using lookup-pedersen in merkle trees? crypto::generators::init_generator_data(); } @@ -18,7 +19,7 @@ WASM_EXPORT void pedersen__hash_pair(uint8_t const* left, uint8_t const* right, { auto lhs = barretenberg::fr::serialize_from_buffer(left); auto rhs = barretenberg::fr::serialize_from_buffer(right); - auto r = crypto::pedersen_hash::hash_multiple({ lhs, rhs }); + auto r = crypto::pedersen_hash::lookup::hash_multiple({ lhs, rhs }); barretenberg::fr::serialize_to_buffer(r, result); } @@ -26,7 +27,7 @@ WASM_EXPORT void pedersen__hash_multiple(uint8_t const* inputs_buffer, uint8_t* { std::vector to_compress; read(inputs_buffer, to_compress); - auto r = crypto::pedersen_hash::hash_multiple(to_compress); + auto r = crypto::pedersen_hash::lookup::hash_multiple(to_compress); barretenberg::fr::serialize_to_buffer(r, output); } @@ -36,7 +37,7 @@ WASM_EXPORT void pedersen__hash_multiple_with_hash_index(uint8_t const* inputs_b { std::vector to_compress; read(inputs_buffer, to_compress); - auto r = crypto::pedersen_hash::hash_multiple(to_compress, hash_index); + auto r = crypto::pedersen_hash::lookup::hash_multiple(to_compress, hash_index); barretenberg::fr::serialize_to_buffer(r, output); } @@ -54,7 +55,7 @@ WASM_EXPORT uint8_t* pedersen__hash_to_tree(uint8_t const* data) fields.reserve(num_outputs); for (size_t i = 0; fields.size() < num_outputs; i += 2) { - fields.push_back(crypto::pedersen_hash::hash_multiple({ fields[i], fields[i + 1] })); + fields.push_back(crypto::pedersen_hash::lookup::hash_multiple({ fields[i], fields[i + 1] })); } auto buf_size = 4 + num_outputs * sizeof(grumpkin::fq); diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp index c18a426e20..21abc4e8e2 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp @@ -96,9 +96,11 @@ void create_ecdsa_verify_constraints(Composer& composer, const EcdsaSecp256k1Con std::vector rr(new_sig.r.begin(), new_sig.r.end()); std::vector ss(new_sig.s.begin(), new_sig.s.end()); + uint8_t vv = new_sig.v; stdlib::ecdsa::signature sig{ stdlib::byte_array(&composer, rr), - stdlib::byte_array(&composer, ss) }; + stdlib::byte_array(&composer, ss), + stdlib::uint8(&composer, vv) }; pub_key_x_fq.assert_is_in_field(); pub_key_y_fq.assert_is_in_field(); diff --git a/cpp/src/barretenberg/ecc/curves/secp256k1/c_bind.cpp b/cpp/src/barretenberg/ecc/curves/secp256k1/c_bind.cpp new file mode 100644 index 0000000000..107d889d4c --- /dev/null +++ b/cpp/src/barretenberg/ecc/curves/secp256k1/c_bind.cpp @@ -0,0 +1,30 @@ +#include "secp256k1.hpp" + +#define WASM_EXPORT __attribute__((visibility("default"))) + +extern "C" { + +WASM_EXPORT void ecc_secp256k1__mul(uint8_t const* point_buf, uint8_t const* scalar_buf, uint8_t* result) +{ + auto point = from_buffer(point_buf); + auto scalar = from_buffer(scalar_buf); + secp256k1::g1::affine_element r = point * scalar; + write(result, r); +} + +WASM_EXPORT void ecc_secp256k1__get_random_scalar_mod_circuit_modulus(uint8_t* result) +{ + barretenberg::fr output = barretenberg::fr::random_element(); + write(result, output); +} + +WASM_EXPORT void ecc_secp256k1__reduce512_buffer_mod_circuit_modulus(uint8_t* input, uint8_t* result) +{ + uint512_t bigint_input = from_buffer(input); + + uint512_t barretenberg_modulus(barretenberg::fr::modulus); + + uint512_t target_output = bigint_input % barretenberg_modulus; + write(result, target_output.lo); +} +} \ No newline at end of file diff --git a/cpp/src/barretenberg/ecc/curves/secp256k1/c_bind.hpp b/cpp/src/barretenberg/ecc/curves/secp256k1/c_bind.hpp new file mode 100644 index 0000000000..b1b1983239 --- /dev/null +++ b/cpp/src/barretenberg/ecc/curves/secp256k1/c_bind.hpp @@ -0,0 +1,12 @@ +#include "secp256k1.hpp" + +#define WASM_EXPORT __attribute__((visibility("default"))) + +extern "C" { + +WASM_EXPORT void ecc_secp256k1__mul(uint8_t const* point_buf, uint8_t const* scalar_buf, uint8_t* result); + +WASM_EXPORT void ecc_secp256k1__get_random_scalar_mod_circuit_modulus(uint8_t* result); + +WASM_EXPORT void ecc_secp256k1__reduce512_buffer_mod_circuit_modulus(uint8_t* input, uint8_t* result); +} diff --git a/cpp/src/barretenberg/ecc/groups/affine_element.hpp b/cpp/src/barretenberg/ecc/groups/affine_element.hpp index 82ba93bfef..8d85003a51 100644 --- a/cpp/src/barretenberg/ecc/groups/affine_element.hpp +++ b/cpp/src/barretenberg/ecc/groups/affine_element.hpp @@ -62,6 +62,13 @@ template class alignas(64) affine_el constexpr bool on_curve() const noexcept; + /** + * @brief Samples a random point on the curve. + * + * @return A randomly chosen point on the curve + */ + static affine_element random_element(numeric::random::Engine* engine = nullptr) noexcept; + /** * @brief Hash a seed value to curve. * diff --git a/cpp/src/barretenberg/ecc/groups/affine_element_impl.hpp b/cpp/src/barretenberg/ecc/groups/affine_element_impl.hpp index f5b2e1705a..c1e61e729f 100644 --- a/cpp/src/barretenberg/ecc/groups/affine_element_impl.hpp +++ b/cpp/src/barretenberg/ecc/groups/affine_element_impl.hpp @@ -229,5 +229,38 @@ affine_element affine_element::hash_to_curve(const uint64_ return affine_element(x_out, y_out_); } + +template +affine_element affine_element::random_element(numeric::random::Engine* engine) noexcept +{ + if (engine == nullptr) { + engine = &numeric::random::get_engine(); + } + + bool found_one = false; + Fq yy; + Fq x; + Fq y; + while (!found_one) { + // Sample a random x-coordinate and check if it satisfies curve equation. + x = Fq::random_element(engine); + yy = x.sqr() * x + T::b; + if constexpr (T::has_a) { + yy += (x * T::a); + } + auto [found_root, y1] = yy.sqrt(); + y = y1; + + // Negate the y-coordinate based on a randomly sampled bit. + bool random_bit = (engine->get_random_uint8() & 1); + if (random_bit) { + y = -y; + } + + found_one = found_root; + } + return affine_element(x, y); +} + } // namespace group_elements } // namespace barretenberg diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp index 1864557af4..ea5f890fd5 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp @@ -2,6 +2,7 @@ #include "barretenberg/crypto/ecdsa/ecdsa.hpp" #include "../../primitives/byte_array/byte_array.hpp" +#include "../../primitives/uint/uint.hpp" #include "../../primitives/composers/composers_fwd.hpp" namespace proof_system::plonk { @@ -11,6 +12,7 @@ namespace ecdsa { template struct signature { stdlib::byte_array r; stdlib::byte_array s; + stdlib::uint8 v; }; template @@ -25,9 +27,11 @@ static signature from_witness(Composer* ctx, const crypto::ecdsa::sign std::vector s_vec(std::begin(input.s), std::end(input.s)); stdlib::byte_array r(ctx, r_vec); stdlib::byte_array s(ctx, s_vec); + stdlib::uint8 v(ctx, input.v); signature out; out.r = r; out.s = s; + out.v = v; return out; } diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp index b1edc24d84..405d28f33c 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp @@ -35,8 +35,11 @@ TEST(stdlib_ecdsa, verify_signature) std::vector rr(signature.r.begin(), signature.r.end()); std::vector ss(signature.s.begin(), signature.s.end()); + uint8_t vv = signature.v; - stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), curve::byte_array_ct(&composer, ss) }; + stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), + curve::byte_array_ct(&composer, ss), + stdlib::uint8(&composer, vv) }; curve::byte_array_ct message(&composer, message_string); diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp index 12355453d6..4c23d3d66c 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp @@ -13,6 +13,36 @@ bool_t verify_signature(const stdlib::byte_array& message, { Composer* ctx = message.get_context() ? message.get_context() : public_key.x.context; + /** + * Check if recovery id v is either 27 ot 28. + * + * The v in an (r, s, v) ecdsa signature is the 8-bit recovery id s.t. v ∈ {0, 1, 2, 3}. + * It is used to recover signing public key from an ecdsa signature. In practice, the value + * of v is offset by 27 following the convention from the original bitcoin whitepaper. + * + * The value of v depends on the the point R = (x, y) s.t. r = x % |Fr| + * 0: y is even && x < |Fr| (x = r) + * 1: y is odd && x < |Fr| (x = r) + * 2: y is even && |Fr| <= x < |Fq| (x = r + |Fr|) + * 3: y is odd && |Fr| <= x < |Fq| (x = r + |Fr|) + * + * It is highly unlikely for x be be in [|Fr|, |Fq|) for the secp256k1 curve because: + * P(|Fr| <= x < |Fq|) = 1 - |Fr|/|Fq| ≈ 0. + * Therefore, it is reasonable to assume that the value of v will always be 0 or 1 + * (i.e. 27 or 28 with offset). In fact, the ethereum yellow paper [1] only allows v to be 27 or 28 + * and considers signatures with v ∈ {29, 30} to be non-standard. + * + * TODO(Suyash): EIP-155 allows v > 35 to ensure different v on different chains. + * Do we need to consider that in our circuits? + * + * References: + * [1] Ethereum yellow paper, Appendix E: https://ethereum.github.io/yellowpaper/paper.pdf + * [2] EIP-155: https://eips.ethereum.org/EIPS/eip-155 + * + */ + field_t(sig.v).assert_is_in_set({ field_t(27), field_t(28) }, + "signature is non-standard"); + stdlib::byte_array hashed_message = static_cast>(stdlib::sha256(message)); diff --git a/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp b/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp index 2a1829ce22..3b3bd34f67 100644 --- a/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp +++ b/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield.hpp @@ -160,7 +160,13 @@ template class bigfield { field_t lo = binary_basis_limbs[0].element + (binary_basis_limbs[1].element * shift_1); field_t hi = binary_basis_limbs[2].element + (binary_basis_limbs[3].element * shift_1); // n.b. this only works if NUM_LIMB_BITS * 2 is divisible by 8 - ASSERT((NUM_LIMB_BITS / 8) * 8 == NUM_LIMB_BITS); + // + // We are packing two bigfield limbs each into the field elements `lo` and `hi`. + // Thus, each of `lo` and `hi` will contain (NUM_LIMB_BITS * 2) bits. We then convert + // `lo` and `hi` to `byte_array` each containing ((NUM_LIMB_BITS * 2) / 8) bytes. + // Therefore, it is necessary for (NUM_LIMB_BITS * 2) to be divisible by 8 for correctly + // converting `lo` and `hi` to `byte_array`s. + ASSERT((NUM_LIMB_BITS * 2 / 8) * 8 == NUM_LIMB_BITS * 2); result.write(byte_array(hi, 32 - (NUM_LIMB_BITS / 4))); result.write(byte_array(lo, (NUM_LIMB_BITS / 4))); return result;