Skip to content

Commit

Permalink
Add a sample of jets for secp256k1 operations.
Browse files Browse the repository at this point in the history
Specifically we add
* sqrt for field elemnts
* offsetPoint for group elements (adding a point in affine coordinates to a point in jacobian coordinates)
* ecMult: Add a scalar multiple of a group element (in jacobian coordinates) to a scalar multiple of the secp256k1 generator point
* schnorrAssert: Decide if a schnorr signature is valid for a given public key-message pair.

These jets "exercise" the various domains of secp256k1.
Later we will add a whole host of jets for secp256k1 functionality, but for now ecMult and schnorrAssert are the most useful functions.
  • Loading branch information
roconnor-blockstream committed Mar 26, 2020
1 parent 184d216 commit 75f4e7d
Show file tree
Hide file tree
Showing 24 changed files with 4,941 additions and 9 deletions.
6 changes: 5 additions & 1 deletion C/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
OBJS := bitstream.o dag.o deserialize.o eval.o frame.o jets.o sha256.o type.o typeInference.o primitive/elements.o primitive/elements/jets.o primitive/elements/primitive.o
OBJS := bitstream.o dag.o deserialize.o eval.o frame.o jets.o jets-secp256k1.o sha256.o type.o typeInference.o primitive/elements.o primitive/elements/jets.o primitive/elements/primitive.o
TEST_OBJS := test.o hashBlock.o schnorr0.o schnorr6.o primitive/elements/checkSigHashAllTx1.o

# From https://fastcompression.blogspot.com/2019/01/compiler-warnings.html
Expand All @@ -22,6 +22,10 @@ endif

LDLIBS := -lsha256compression

# libsecp256k1 is full of conversion warnings, so we compile jets-secp256k1.c separately.
jets-secp256k1.o: jets-secp256k1.c
$(CC) -c $(CFLAGS) $(CWARN) -Wno-conversion $(CPPFLAGS) -o $@ $<

primitive/elements/jets.o: primitive/elements/jets.c
$(CC) -c $(CFLAGS) $(CWARN) -Wno-switch-enum -Wswitch $(CPPFLAGS) -o $@ $<

Expand Down
215 changes: 215 additions & 0 deletions C/jets-secp256k1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#include "jets.h"

#include "sha256.h"
#include "secp256k1/secp256k1_impl.h"

/* Copy an array of 'uint32_t's into an array of 'unsigned char' by transforming each 32 bit values into four 8-bit values
* written in big endian order.
*
* Precondition: unsigned char out[4*len];
* uint32_t in[len]
*/
static inline void unpack32s(unsigned char* out, const uint32_t* in, size_t len) {
while (len) {
WriteBE32(out, *in);
out += 4;
++in;
--len;
}
}

/* Read a secp256k1 10x32 field element value from the 'src' frame, advancing the cursor 320 cells.
*
* Precondition: '*src' is a valid read frame for 320 more cells;
* NULL != r;
*/
static inline void read_fe(secp256k1_fe* r, frameItem* src) {
read32s(r->n, 10, src);
}

/* Write a secp256k1 10x32 field element value to the 'dst' frame, advancing the cursor 320 cells.
*
* Precondition: '*dst' is a valid write frame for 320 more cells;
* NULL != r;
*/
static inline void write_fe(frameItem* dst, const secp256k1_fe* r) {
write32s(dst, r->n, 10);
}

/* Skip 320 cells, the size of a secp256k1 10x32 field element value, in the 'dst' frame.
*
* Precondition: '*dst' is a valid write frame for 320 more cells;
*/
static inline void skip_fe(frameItem* dst) {
skipBits(dst, 32*10);
}

/* Read a (non-infinity) secp256k1 affine group element value from the 'src' frame, advancing the cursor 640 cells.
*
* Precondition: '*src' is a valid read frame for 640 more cells;
* NULL != r;
*/
static inline void read_ge(secp256k1_ge* r, frameItem* src) {
read_fe(&r->x, src);
read_fe(&r->y, src);
r->infinity = 0;
}

/* Read a secp256k1 jacobian group element value from the 'src' frame, advancing the cursor 960 cells.
*
* Precondition: '*src' is a valid read frame for 960 more cells;
* NULL != r;
*/
static inline void read_gej(secp256k1_gej* r, frameItem* src) {
read_fe(&r->x, src);
read_fe(&r->y, src);
read_fe(&r->z, src);
r->infinity = secp256k1_fe_is_zero(&r->z);
}

/* Write a secp256k1 jacobian group element value to the 'dst' frame, advancing the cursor 960 cells.
* If 'r->infinity' then we write an fe_zero value to the 'z' coordinate in the 'dst' frame instead of 'r->z'.
*
* Precondition: '*dst' is a valid write frame for 960 more cells;
* NULL != r;
*/
static inline void write_gej(frameItem* dst, const secp256k1_gej* r) {
write_fe(dst, &r->x);
write_fe(dst, &r->y);
if (r->infinity) {
write32s(dst, (uint32_t[10]){0}, 10);
} else {
write_fe(dst, &r->z);
}
}

/* Read a secp256k1 scalar element value from the 'src' frame, advancing the cursor 256 cells.
* The secp256k1 8x32 scalar representation puts the 32 least significant bytes into the first array element;
* However Simplicity uses a standard big-endian 256-bit word to represent scalar values.
* Thus it is necessary to fill the secp256k1 scalar array in reverse order.
*
* Precondition: '*src' is a valid read frame for 256 more cells;
* NULL != r;
*/
static inline void read_scalar(secp256k1_scalar* r, frameItem* src) {
r->d[7] = (uint32_t)read32(src);
r->d[6] = (uint32_t)read32(src);
r->d[5] = (uint32_t)read32(src);
r->d[4] = (uint32_t)read32(src);
r->d[3] = (uint32_t)read32(src);
r->d[2] = (uint32_t)read32(src);
r->d[1] = (uint32_t)read32(src);
r->d[0] = (uint32_t)read32(src);
}

/* Write a secp256k1 scalar element value to the 'dst' frame, advancing the cursor 256 cells.
* The secp256k1 8x32 scalar representation puts the 32 least significant bytes into the first array element;
* However Simplicity uses a standard big-endian 256-bit word to represent scalar values.
* Thus it is necessary to read from the secp256k1 scalar array in reverse order.
*
* Precondition: '*dst' is a valid write frame for 256 more cells;
* NULL != r;
*/
static inline void write_scalar(frameItem* dst, const secp256k1_scalar* r) {
write32(dst, r->d[7]);
write32(dst, r->d[6]);
write32(dst, r->d[5]);
write32(dst, r->d[4]);
write32(dst, r->d[3]);
write32(dst, r->d[2]);
write32(dst, r->d[1]);
write32(dst, r->d[0]);
}

bool fe_sqrt(frameItem* dst, frameItem src, const txEnv* env) {
(void) env; // env is unused;

secp256k1_fe r, a;
read_fe(&a, &src);
int result = secp256k1_fe_sqrt_var(&r, &a);
if (writeBit(dst, result)) {
write_fe(dst, &r);
} else {
skip_fe(dst);
}
return true;
}

bool offsetPoint(frameItem* dst, frameItem src, const txEnv* env) {
(void) env; // env is unused;

secp256k1_gej r, a;
secp256k1_ge b;
secp256k1_fe rzr;
read_gej(&a, &src);
read_ge(&b, &src);
secp256k1_gej_add_ge_var(&r, &a, &b, &rzr);
write_fe(dst, &rzr);
write_gej(dst, &r);
return true;
}

static struct {
secp256k1_ecmult_context ctx;
char alloc[SECP256K1_ECMULT_CONTEXT_PREALLOCATED_SIZE];
bool initialized;
} ecmult_static;

/* This function will initialize the 'ecmult_static' global variable if it hasn't already been initialized. */
static void ensure_ecmult_static(void) {
if (!ecmult_static.initialized) {
void *prealloc = ecmult_static.alloc;
secp256k1_ecmult_context_init(&ecmult_static.ctx);
secp256k1_ecmult_context_build(&ecmult_static.ctx, &prealloc);
ecmult_static.initialized = secp256k1_ecmult_context_is_built(&ecmult_static.ctx);
}
assert(ecmult_static.initialized);
}

bool ecmult(frameItem* dst, frameItem src, const txEnv* env) {
(void) env; // env is unused;

secp256k1_gej r, a;
secp256k1_scalar na, ng;

ensure_ecmult_static();
read_gej(&a, &src);
read_scalar(&na, &src);
read_scalar(&ng, &src);
secp256k1_ecmult(&ecmult_static.ctx, &r, &a, &na, &ng);

/* This jet's implementation of ecmult is defined to always outputs the jacobian coordinate (1, 1, 0)
* if the result is the point at infinity.
*/
if (r.infinity) {
secp256k1_fe_set_int(&r.x, 1);
secp256k1_fe_set_int(&r.y, 1);
}
write_gej(dst, &r);
return true;
}

bool schnorrAssert(frameItem* dst, frameItem src, const txEnv* env) {
(void) dst; // dst is unused;
(void) env; // env is unused;

uint32_t buf[16];
unsigned char buf_char[64];
secp256k1_xonly_pubkey pubkey;
unsigned char msg[32];
secp256k1_schnorrsig sig;

ensure_ecmult_static();
read32s(buf, 8, &src);
unpack32s(buf_char, buf, 8);
if (!secp256k1_xonly_pubkey_parse(&pubkey, buf_char)) return false;

read32s(buf, 8, &src);
unpack32s(msg, buf, 8);

read32s(buf, 16, &src);
unpack32s(buf_char, buf, 16);
secp256k1_schnorrsig_parse(&sig, buf_char);

return secp256k1_schnorrsig_verify(&ecmult_static.ctx, &sig, msg, &pubkey);
}
5 changes: 5 additions & 0 deletions C/jets.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ bool multiplier32(frameItem* dst, frameItem src, const txEnv* env);
bool fullMultiplier32(frameItem* dst, frameItem src, const txEnv* env);
bool sha256_hashBlock(frameItem* dst, frameItem src, const txEnv* env);

bool fe_sqrt(frameItem* dst, frameItem src, const txEnv* env);
bool offsetPoint(frameItem* dst, frameItem src, const txEnv* env);
bool ecmult(frameItem* dst, frameItem src, const txEnv* env);
bool schnorrAssert(frameItem* dst, frameItem src, const txEnv* env);

#endif
21 changes: 13 additions & 8 deletions C/primitive/elements/primitive.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum TypeNamesForJets {
word128,
word256,
word512,
word1024,
pubkey,
sTwo,
outpnt,
Expand Down Expand Up @@ -92,6 +93,8 @@ size_t mallocBoundVars(unification_var** bound_var, size_t* word256_ix, size_t*
.bound = { .kind = PRODUCT, .arg = { &(*bound_var)[word128], &(*bound_var)[word128] } } };
(*bound_var)[word512] = (unification_var){ .isBound = true,
.bound = { .kind = PRODUCT, .arg = { &(*bound_var)[word256], &(*bound_var)[word256] } } };
(*bound_var)[word1024] = (unification_var){ .isBound = true,
.bound = { .kind = PRODUCT, .arg = { &(*bound_var)[word512], &(*bound_var)[word512] } } };
(*bound_var)[pubkey] = (unification_var){ .isBound = true,
.bound = { .kind = PRODUCT, .arg = { &(*bound_var)[two], &(*bound_var)[word256] } } };
(*bound_var)[sTwo] = (unification_var){ .isBound = true,
Expand Down Expand Up @@ -151,6 +154,7 @@ typedef enum jetName
, FULLSUBTRACTOR32
, FULLMULTIPLIER32
, SHA256_HASHBLOCK
, SCHNORRASSERT
, VERSION
, LOCKTIME
, INPUTISPEGIN
Expand Down Expand Up @@ -270,14 +274,7 @@ static int32_t decodePrimitive(jetName* result, bitstream* stream) {
assert(false);
UNREACHABLE;
} else {
bit = getBit(stream);
if (bit < 0) return bit;
if (!bit) {
*result = SHA256_HASHBLOCK; return 0;
} else {
fprintf(stderr, "EC jets nodes not yet implemented.\n");
return ERR_DATA_OUT_OF_RANGE;
}
return either(result, SHA256_HASHBLOCK, SCHNORRASSERT, stream);
}
}
}
Expand Down Expand Up @@ -329,6 +326,12 @@ static dag_node jet_node[] = {
, .sourceIx = word256TimesWord512
, .targetIx = word256
},
[SCHNORRASSERT] =
{ .tag = JET
, .jet = schnorrAssert
, .sourceIx = word1024
, .targetIx = one
},
[VERSION] =
{ .tag = JET
, .jet = version
Expand Down Expand Up @@ -564,6 +567,8 @@ static void static_initialize(void) {
MK_JET(MULTIPLIER32, 0x405914c9, 0x524c4873, 0xce5ddb06, 0xfd30d6d5, 0xfc4ac1fa, 0xc0eef8d8, 0x2de6c622, 0x7fb2d2cd);
MK_JET(FULLMULTIPLIER32, 0x89a0ae09, 0x8aff5e9c, 0x40907447, 0x91ff5c8e, 0xe17a8ceb, 0x9e494224, 0xe919deb1, 0x1c5b8af4);
MK_JET(SHA256_HASHBLOCK, 0xeeae47e2, 0xf7876c3b, 0x9cbcd404, 0xa338b089, 0xfdeadf1b, 0x9bb382ec, 0x6e69719d, 0x31baec9a);
MK_JET(SCHNORRASSERT, 0xa1e76928, 0xf5dfd245, 0xf417e465, 0xe067e043, 0xa1070996, 0x497ce766, 0xed95a5c7, 0x85c3c7c1);

#undef MK_JET

}
Expand Down
34 changes: 34 additions & 0 deletions C/secp256k1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
This directory contains a modified copy of the libsecp256k1 branch `2e4ed392e1fd8cb7c64787bde9b67ddc0b463e3d` from <https://github.com/jonasnick/secp256k1/commits/schnorrsig>.
In general, the files in this directory should be compared with the corresponding files in the `src` directory from libsecp256k1.
There are some exceptions however:

* `secp256k1.h` should be compared with `include/secp256k1.h`.
* `secp256k1_impl.h` should be compared with `src/secp256k1.c`.
* `schnorrsig.h` should be compared with `include/secp256k1_schnorrsig.h`.
* `schnorrsig_impl.h` should be compared with `src/modules/schnorrsig/main_impl.h`.

Our use of libsecp256k1 for various jets requires access to the internal functions that are not exposed by the their API, so we cannot use libsecp256k1's normal interface.
Furthermore, because Simplicity has no abstract data types, the specific details of the representation of field and group elements computed by jetted functions ends up being consensus critical.
Therefore, even if libsecp256k1's interface exposed the functionality we needed, we still wouldn't be able perform libsecp256k1 version upgrades because different versions of libsecp256k1 do not guarantee that their functions won't change the representation of computed field and group elements.
Even libsecp256k1's configuration options, including `--enable-endomorphism`, the choice of scalar or field implementation, and the `ECMULT_WINDOW_SIZE`, all can affect the representation of the computed field and group elements.
Therefore we need to fix these options to specific values.

Simplicity computations are on public data and therefore we do not jet functions that manipulate private or secret data.
In particular, we only need to jet variable-time algorithms when there is a choice of variable-time or constant-time algorithms.

In incorporating the libsecp256k1 library into the Simplicity library, there is a tension between making minimal changes to the library versus removing configuration options and other code that, if they were activated, could cause consensus incompatible changes in functionality.
Because we will not be able to easily migrate to newer versions of libsecp256k1 anyways, we have take a heavy-handed approach to trimming unused configuration options, dead code, functions specific to working with secret data, etc.
In some cases we have made minor code changes:

* Some casts have been slightly modified to remove warnings caused by our choice of GCC flags.
* The definition of `ALIGNMENT` has been modified to take advantage of C11's `max_align_t`.
* `secp256k1_fe_sqrt` has been modified to call `secp256k1_fe_equal_var` (as `secp256k1_fe_equal` has been removed). The function has been renamed to `secp256k1_fe_sqrt_var` and similar for other indirect callers.
* `secp256k1_scalar_chacha20` was removed due to its non-portable use of the `WORDS_BIGENDIAN` flag. It could be returned with a more portable implementation.
* The uses of secp256k1's `hash.h` for Schnorr signatures has been replaced with calls to Simplicity's internal `sha256.h` implementation. This removes the duplication of functionality and replaces the non-portable use of the `WORDS_BIGENDIAN` flag in `hash_impl.h` with our portable implementation.
* Any functions making heap allocations have been removed. (There are very few to begin with.)
* The constant variable `SECP256K1_ECMULT_CONTEXT_PREALLOCATED_SIZE` has been replaced with a macro so that it can be used as a constant expression for static array allocations.
* `ARG_CHECK` has been replaced by `VERIFY_CHECK` so that callbacks can be removed.
* Callbacks have been removed.
* `secp256k1_context` has been removed and replaced by `secp256k1_ecmult_context` in those few places where anything is needed. No other parts of the of `secp256k1_context` are used in Simplicity jets.
* The `compressed` argument to `secp256k1_eckey_pubkey_serialize` has been eliminated and the code modified to only serialize compressed keys.
* The `secp256k1_schnorrsig_serialize` and `secp256k1_schnorrsig_parse` function always succeed (due to the `ARG_CHECK` replacement), so their return types have been removed.
24 changes: 24 additions & 0 deletions C/secp256k1/eckey.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**********************************************************************
* Copyright (c) 2013, 2014 Pieter Wuille *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/

#ifndef SECP256K1_ECKEY_H
#define SECP256K1_ECKEY_H

#include <stddef.h>

#include "group.h"
#include "scalar.h"
#include "ecmult.h"

static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char *pub, size_t size);
static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size);

#if 0
static int secp256k1_eckey_pubkey_tweak_add(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak);
static int secp256k1_eckey_pubkey_tweak_mul(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak);
#endif

#endif /* SECP256K1_ECKEY_H */
Loading

0 comments on commit 75f4e7d

Please sign in to comment.