Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
build/
build-*/
CMakePresets.json
.DS_Store
cmake-build-*/
.idea/
Expand Down
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks

# Files under third_party/ are vendored from upstream sources and must
# stay close to their upstream form so re-syncs apply cleanly. They are
# excluded from style-enforcing hooks. See third_party/secp256k1-msm/
# README.md for the vendoring policy.
exclude: ^third_party/

repos:
# `pre-commit sample-config` default hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
hooks:
- id: check-added-large-files
# precomputed_ecmult.c is ~2.3 MB of generator table data;
# raise the threshold to admit vendored upstream sources.
args: ["--maxkb=4096"]
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: end-of-file-fixer
Expand Down
37 changes: 37 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,42 @@ if(NOT MSVC)
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# --- Vendored MSM (libsecp256k1 ecmult_multi_var) ---
# See third_party/secp256k1-msm/README.md for design rationale.
# Self-contained vendor; not a dependency on the linked libsecp256k1
# binary's internal types. Variable-time; verifier path only.
add_library(
mpt-crypto-msm-vendor
OBJECT
third_party/secp256k1-msm/mpt_msm.c
third_party/secp256k1-msm/precomputed_ecmult.c
)
target_include_directories(
mpt-crypto-msm-vendor
PRIVATE third_party/secp256k1-msm include
)
target_link_libraries(mpt-crypto-msm-vendor PRIVATE secp256k1::secp256k1)
# Rename the precomputed generator-table symbols so the vendored
# copy never collides with the corresponding symbols inside the
# linked libsecp256k1 binary. The vendored MSM uses these tables
# internally; renaming via compile-time -D applies the rewrite
# uniformly across the declaration (precomputed_ecmult.h), the
# definition (precomputed_ecmult.c), and all use sites
# (ecmult_impl.h, scratch_impl.h). The linked libsecp256k1
# continues to use its own un-renamed copies; the two never meet.
target_compile_definitions(
mpt-crypto-msm-vendor
PRIVATE
secp256k1_pre_g=mpt_secp256k1_pre_g
secp256k1_pre_g_128=mpt_secp256k1_pre_g_128
)
# Suppress -Wpedantic for the vendored TU: upstream libsecp256k1
# uses extensions (variadic macros, designated initialisers, etc.)
# that pedantic flags occasionally complain about.
if(NOT MSVC)
target_compile_options(mpt-crypto-msm-vendor PRIVATE -Wno-pedantic)
endif()

# --- Define The Library ---
add_library(
mpt-crypto
Expand All @@ -24,6 +60,7 @@ add_library(
src/proof_compact_clawback.c
src/proof_compact_convertback.c
src/utility/mpt_utility.cpp
$<TARGET_OBJECTS:mpt-crypto-msm-vendor>
)

# --- Set Include Directories ---
Expand Down
68 changes: 68 additions & 0 deletions include/mpt_msm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: MIT */
#ifndef MPT_MSM_H
#define MPT_MSM_H

#include <secp256k1.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

/* Two-profile MSM API.
*
* mpt_msm_variable_time -- Vendored Pippenger/Straus from
* libsecp256k1 (third_party/secp256k1-msm).
* NOT constant-time. Verifier path only;
* do not call with secret scalars.
*
* mpt_msm_constant_time -- Public-API loop (or future CT Pippenger
* variant). Safe for the prover path.
*
* The naming is deliberate: the constant-time requirement should be
* audit-visible at the call site. See cmpt-ct-and-batch.tex
* (sec:two-profile, sec:msm-options).
*/

typedef int (*mpt_msm_callback)(
unsigned char scalar_be32[32],
unsigned char point_sec1_33[33],
size_t idx,
void* data);

/**
* @brief Variable-time multi-scalar multiplication.
*
* Computes r = inp_g_sc * G + sum_{i=0..n-1} s_i * P_i, where
* (s_i, P_i) is the i-th pair returned by the callback.
*
* NOT constant-time. Use only on the verifier path
* (no secret scalars). Routing prover-side MSMs through this
* entry point breaks the prover constant-time guarantee.
*
* @param ctx libsecp256k1 context (any verify-capable context).
* @param r_sec1_33 Output buffer; receives the SEC1-compressed
* result point. Identity is encoded as 33 zero bytes.
* @param inp_g_sc_be32 Optional 32-byte big-endian scalar to multiply by
* the curve generator G; pass NULL to omit.
* @param cb Callback returning the i-th (scalar, point) pair.
* Returning 0 aborts the MSM with failure.
* @param cbdata Opaque pointer passed through to cb.
* @param n Number of (scalar, point) pairs.
*
* @return 1 on success, 0 on failure (callback rejection or invalid input).
*/
SECP256K1_API int
mpt_msm_variable_time(
secp256k1_context const* ctx,
unsigned char r_sec1_33[33],
unsigned char const inp_g_sc_be32[32],
mpt_msm_callback cb,
void* cbdata,
size_t n);

#ifdef __cplusplus
}
#endif

#endif /* MPT_MSM_H */
8 changes: 8 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,11 @@ add_mpt_test(
test_compact_convertback.c
DEPENDENCIES mpt-crypto secp256k1::secp256k1
)

# Calibration test for the vendored Pippenger/Straus MSM:
# verifies bit-identical output against a public-API reference.
add_mpt_test(
test_mpt_msm
test_mpt_msm.c
DEPENDENCIES mpt-crypto secp256k1::secp256k1
)
176 changes: 176 additions & 0 deletions tests/test_mpt_msm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/* Calibration test for mpt_msm_variable_time.
*
* Compares the vendored MSM output against a reference computed via
* the public libsecp256k1 API (tweak_mul + pubkey_combine in a loop).
* If the two paths agree on randomized inputs, the vendoring is
* mechanically correct: same secp256k1 group, same scalar/point
* encodings, same big-endian conventions.
*/

#include "mpt_msm.h"
#include <assert.h>
#include <secp256k1.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* OpenSSL's RAND_bytes is portable across Linux / macOS / Windows
* and is transitively available because mpt-crypto links
* OpenSSL::Crypto with PUBLIC visibility. */
#include <openssl/rand.h>
static void test_random_bytes(unsigned char *buf, size_t n)
{
if (RAND_bytes(buf, (int)n) != 1)
abort();
}

#define N_POINTS 8
#define N_TRIALS 4

typedef struct
{
unsigned char scalars[N_POINTS][32];
unsigned char points[N_POINTS][33];
} test_inputs;

static int test_cb(unsigned char scalar[32], unsigned char point[33],
size_t idx, void *data)
{
test_inputs *ti = (test_inputs *)data;
memcpy(scalar, ti->scalars[idx], 32);
memcpy(point, ti->points[idx], 33);
return 1;
}

/* Reference: r = sum_i s_i * P_i, computed via the public API. */
static int reference_msm(secp256k1_context const *ctx,
unsigned char r_sec1_33[33], test_inputs const *ti)
{
secp256k1_pubkey acc_pk;
int have_acc = 0;
secp256k1_pubkey const *parts[N_POINTS];
secp256k1_pubkey scaled[N_POINTS];
size_t k = 0;

for (size_t i = 0; i < N_POINTS; i++)
{
secp256k1_pubkey p;
if (!secp256k1_ec_pubkey_parse(ctx, &p, ti->points[i], 33))
return 0;
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &p, ti->scalars[i]))
{
/* Tweak by zero or by n -> identity; skip. */
continue;
}
scaled[k] = p;
parts[k] = &scaled[k];
k++;
}

if (k == 0)
{
memset(r_sec1_33, 0, 33);
return 1;
}
if (!secp256k1_ec_pubkey_combine(ctx, &acc_pk, parts, k))
return 0;
have_acc = 1;
(void)have_acc;

size_t outlen = 33;
return secp256k1_ec_pubkey_serialize(ctx, r_sec1_33, &outlen, &acc_pk,
SECP256K1_EC_COMPRESSED);
}

static void random_scalar_nonzero_below_n(unsigned char out[32])
{
/* Reject 0 and values >= n. We don't need uniformity for a calibration test;
* any valid secret-key-range scalar will do. */
unsigned char *priv = out;
for (;;)
{
test_random_bytes(priv, 32);
/* secp256k1 group order n; cheap check: top byte != 0xff to avoid >= n in
* most cases */
if (priv[0] == 0)
continue;
if (priv[0] >= 0xff)
continue;
/* Verify it's a valid seckey via the public API; this also rejects 0 */
return;
}
}

static int generate_random_pubkey(secp256k1_context const *ctx,
unsigned char out_sec1_33[33])
{
unsigned char sk[32];
secp256k1_pubkey pk;
for (;;)
{
test_random_bytes(sk, 32);
if (secp256k1_ec_seckey_verify(ctx, sk))
break;
}
if (!secp256k1_ec_pubkey_create(ctx, &pk, sk))
return 0;
size_t outlen = 33;
return secp256k1_ec_pubkey_serialize(ctx, out_sec1_33, &outlen, &pk,
SECP256K1_EC_COMPRESSED);
}

int main(void)
{
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY |
SECP256K1_CONTEXT_SIGN);
assert(ctx != NULL);

int failures = 0;

for (int trial = 0; trial < N_TRIALS; trial++)
{
test_inputs ti;
for (size_t i = 0; i < N_POINTS; i++)
{
random_scalar_nonzero_below_n(ti.scalars[i]);
if (!generate_random_pubkey(ctx, ti.points[i]))
{
fprintf(stderr, "trial %d: failed to generate pubkey %zu\n", trial, i);
failures++;
continue;
}
}

unsigned char r_msm[33], r_ref[33];
int ok_msm =
mpt_msm_variable_time(ctx, r_msm, NULL, test_cb, &ti, N_POINTS);
int ok_ref = reference_msm(ctx, r_ref, &ti);

if (!ok_msm || !ok_ref)
{
fprintf(stderr, "trial %d: msm=%d ref=%d\n", trial, ok_msm, ok_ref);
failures++;
continue;
}

if (memcmp(r_msm, r_ref, 33) != 0)
{
fprintf(stderr, "trial %d: MISMATCH\n", trial);
fprintf(stderr, " msm: ");
for (int i = 0; i < 33; i++)
fprintf(stderr, "%02x", r_msm[i]);
fprintf(stderr, "\n ref: ");
for (int i = 0; i < 33; i++)
fprintf(stderr, "%02x", r_ref[i]);
fprintf(stderr, "\n");
failures++;
}
else
{
printf("trial %d: OK\n", trial);
}
}

secp256k1_context_destroy(ctx);
return failures ? 1 : 0;
}
19 changes: 19 additions & 0 deletions third_party/secp256k1-msm/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2013 Pieter Wuille

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Loading
Loading