Add post-quantum wallet support and PQ transaction signing

This commit is contained in:
Vexta Project 2026-09-05 21:22:13 +00:00
commit ba44de3cfd
67 changed files with 10102 additions and 269 deletions

2
.gitignore vendored
View file

@ -8,3 +8,5 @@ local.properties
*.keystore
keystore.properties
app/build.gradle.kts.backup-*
app/.cxx/

View file

@ -17,6 +17,7 @@ if (keystorePropertiesFile.exists()) {
android {
namespace = "org.vextaproject.wallet"
compileSdk = 36
ndkVersion = "27.3.13750724"
defaultConfig {
applicationId = "org.vextaproject.wallet"
@ -42,7 +43,19 @@ android {
}
}
externalNativeBuild {
cmake {
version = "3.22.1"
path = file("src/main/cpp/CMakeLists.txt")
}
}
buildTypes {
getByName("debug") {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
}
getByName("release") {
if (keystorePropertiesFile.exists()) {
signingConfig = signingConfigs.getByName("release")

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.22.1)
project(vextapq LANGUAGES C CXX)
add_library(
vextapq
SHARED
vexta_pq_jni.cpp
vexta_pq_randombytes.cpp
pq/pq.cpp
pq/mldsa65/fips202.c
pq/mldsa65/ntt.c
pq/mldsa65/packing.c
pq/mldsa65/poly.c
pq/mldsa65/polyvec.c
pq/mldsa65/reduce.c
pq/mldsa65/rounding.c
pq/mldsa65/sign.c
pq/mldsa65/symmetric-shake.c
pq/sphincsplus/address.c
pq/sphincsplus/fips202.c
pq/sphincsplus/fors.c
pq/sphincsplus/hash_sha2.c
pq/sphincsplus/merkle.c
pq/sphincsplus/sha2.c
pq/sphincsplus/sign.c
pq/sphincsplus/thash_sha2_simple.c
pq/sphincsplus/utils.c
pq/sphincsplus/utilsx1.c
pq/sphincsplus/wots.c
pq/sphincsplus/wotsx1.c
)
target_include_directories(
vextapq
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/pq
${CMAKE_CURRENT_SOURCE_DIR}/pq/mldsa65
${CMAKE_CURRENT_SOURCE_DIR}/pq/sphincsplus
)
target_compile_definitions(
vextapq
PRIVATE
DILITHIUM_MODE=3
PARAMS=sphincs-sha2-128s
)
find_library(log-lib log)
target_link_libraries(
vextapq
${log-lib}
)

View file

@ -0,0 +1,7 @@
Public Domain (https://creativecommons.org/share-your-work/public-domain/cc0/);
or Apache 2.0 License (https://www.apache.org/licenses/LICENSE-2.0.html) or GPL 2.0 License (http://www.gnu.org/licenses/gpl-2.0.html).
For Keccak and the random number generator
we are using public-domain code from sources
and by authors listed in comments on top of
the respective files.

View file

@ -0,0 +1,98 @@
#ifndef API_H
#define API_H
#include <stddef.h>
#include <stdint.h>
#define pqcrystals_dilithium2_PUBLICKEYBYTES 1312
#define pqcrystals_dilithium2_SECRETKEYBYTES 2560
#define pqcrystals_dilithium2_BYTES 2420
#define pqcrystals_dilithium2_ref_PUBLICKEYBYTES pqcrystals_dilithium2_PUBLICKEYBYTES
#define pqcrystals_dilithium2_ref_SECRETKEYBYTES pqcrystals_dilithium2_SECRETKEYBYTES
#define pqcrystals_dilithium2_ref_BYTES pqcrystals_dilithium2_BYTES
int pqcrystals_dilithium2_ref_keypair(uint8_t *pk, uint8_t *sk);
int pqcrystals_dilithium2_ref_signature(uint8_t *sig, size_t *siglen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *sk);
int pqcrystals_dilithium2_ref(uint8_t *sm, size_t *smlen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *sk);
int pqcrystals_dilithium2_ref_verify(const uint8_t *sig, size_t siglen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *pk);
int pqcrystals_dilithium2_ref_open(uint8_t *m, size_t *mlen,
const uint8_t *sm, size_t smlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *pk);
#define pqcrystals_dilithium3_PUBLICKEYBYTES 1952
#define pqcrystals_dilithium3_SECRETKEYBYTES 4032
#define pqcrystals_dilithium3_BYTES 3309
#define pqcrystals_dilithium3_ref_PUBLICKEYBYTES pqcrystals_dilithium3_PUBLICKEYBYTES
#define pqcrystals_dilithium3_ref_SECRETKEYBYTES pqcrystals_dilithium3_SECRETKEYBYTES
#define pqcrystals_dilithium3_ref_BYTES pqcrystals_dilithium3_BYTES
int pqcrystals_dilithium3_ref_keypair(uint8_t *pk, uint8_t *sk);
int pqcrystals_dilithium3_ref_signature(uint8_t *sig, size_t *siglen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *sk);
int pqcrystals_dilithium3_ref(uint8_t *sm, size_t *smlen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *sk);
int pqcrystals_dilithium3_ref_verify(const uint8_t *sig, size_t siglen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *pk);
int pqcrystals_dilithium3_ref_open(uint8_t *m, size_t *mlen,
const uint8_t *sm, size_t smlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *pk);
#define pqcrystals_dilithium5_PUBLICKEYBYTES 2592
#define pqcrystals_dilithium5_SECRETKEYBYTES 4896
#define pqcrystals_dilithium5_BYTES 4627
#define pqcrystals_dilithium5_ref_PUBLICKEYBYTES pqcrystals_dilithium5_PUBLICKEYBYTES
#define pqcrystals_dilithium5_ref_SECRETKEYBYTES pqcrystals_dilithium5_SECRETKEYBYTES
#define pqcrystals_dilithium5_ref_BYTES pqcrystals_dilithium5_BYTES
int pqcrystals_dilithium5_ref_keypair(uint8_t *pk, uint8_t *sk);
int pqcrystals_dilithium5_ref_signature(uint8_t *sig, size_t *siglen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *sk);
int pqcrystals_dilithium5_ref(uint8_t *sm, size_t *smlen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *sk);
int pqcrystals_dilithium5_ref_verify(const uint8_t *sig, size_t siglen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *pk);
int pqcrystals_dilithium5_ref_open(uint8_t *m, size_t *mlen,
const uint8_t *sm, size_t smlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *pk);
#endif

View file

@ -0,0 +1,27 @@
#ifndef CONFIG_H
#define CONFIG_H
//#define DILITHIUM_MODE 2
#define DILITHIUM_RANDOMIZED_SIGNING
//#define USE_RDPMC
//#define DBENCH
#ifndef DILITHIUM_MODE
#define DILITHIUM_MODE 2
#endif
#if DILITHIUM_MODE == 2
#define CRYPTO_ALGNAME "Dilithium2"
#define DILITHIUM_NAMESPACETOP pqcrystals_dilithium2_ref
#define DILITHIUM_NAMESPACE(s) pqcrystals_dilithium2_ref_##s
#elif DILITHIUM_MODE == 3
#define CRYPTO_ALGNAME "Dilithium3"
#define DILITHIUM_NAMESPACETOP pqcrystals_dilithium3_ref
#define DILITHIUM_NAMESPACE(s) pqcrystals_dilithium3_ref_##s
#elif DILITHIUM_MODE == 5
#define CRYPTO_ALGNAME "Dilithium5"
#define DILITHIUM_NAMESPACETOP pqcrystals_dilithium5_ref
#define DILITHIUM_NAMESPACE(s) pqcrystals_dilithium5_ref_##s
#endif
#endif

View file

@ -0,0 +1,774 @@
/* Based on the public domain implementation in crypto_hash/keccakc512/simple/ from
* http://bench.cr.yp.to/supercop.html by Ronny Van Keer and the public domain "TweetFips202"
* implementation from https://twitter.com/tweetfips202 by Gilles Van Assche, Daniel J. Bernstein,
* and Peter Schwabe */
#include <stddef.h>
#include <stdint.h>
#include "fips202.h"
#define NROUNDS 24
#define ROL(a, offset) ((a << offset) ^ (a >> (64-offset)))
/*************************************************
* Name: load64
*
* Description: Load 8 bytes into uint64_t in little-endian order
*
* Arguments: - const uint8_t *x: pointer to input byte array
*
* Returns the loaded 64-bit unsigned integer
**************************************************/
static uint64_t load64(const uint8_t x[8]) {
unsigned int i;
uint64_t r = 0;
for(i=0;i<8;i++)
r |= (uint64_t)x[i] << 8*i;
return r;
}
/*************************************************
* Name: store64
*
* Description: Store a 64-bit integer to array of 8 bytes in little-endian order
*
* Arguments: - uint8_t *x: pointer to the output byte array (allocated)
* - uint64_t u: input 64-bit unsigned integer
**************************************************/
static void store64(uint8_t x[8], uint64_t u) {
unsigned int i;
for(i=0;i<8;i++)
x[i] = u >> 8*i;
}
/* Keccak round constants */
const uint64_t KeccakF_RoundConstants[NROUNDS] = {
(uint64_t)0x0000000000000001ULL,
(uint64_t)0x0000000000008082ULL,
(uint64_t)0x800000000000808aULL,
(uint64_t)0x8000000080008000ULL,
(uint64_t)0x000000000000808bULL,
(uint64_t)0x0000000080000001ULL,
(uint64_t)0x8000000080008081ULL,
(uint64_t)0x8000000000008009ULL,
(uint64_t)0x000000000000008aULL,
(uint64_t)0x0000000000000088ULL,
(uint64_t)0x0000000080008009ULL,
(uint64_t)0x000000008000000aULL,
(uint64_t)0x000000008000808bULL,
(uint64_t)0x800000000000008bULL,
(uint64_t)0x8000000000008089ULL,
(uint64_t)0x8000000000008003ULL,
(uint64_t)0x8000000000008002ULL,
(uint64_t)0x8000000000000080ULL,
(uint64_t)0x000000000000800aULL,
(uint64_t)0x800000008000000aULL,
(uint64_t)0x8000000080008081ULL,
(uint64_t)0x8000000000008080ULL,
(uint64_t)0x0000000080000001ULL,
(uint64_t)0x8000000080008008ULL
};
/*************************************************
* Name: KeccakF1600_StatePermute
*
* Description: The Keccak F1600 Permutation
*
* Arguments: - uint64_t *state: pointer to input/output Keccak state
**************************************************/
static void KeccakF1600_StatePermute(uint64_t state[25])
{
int round;
uint64_t Aba, Abe, Abi, Abo, Abu;
uint64_t Aga, Age, Agi, Ago, Agu;
uint64_t Aka, Ake, Aki, Ako, Aku;
uint64_t Ama, Ame, Ami, Amo, Amu;
uint64_t Asa, Ase, Asi, Aso, Asu;
uint64_t BCa, BCe, BCi, BCo, BCu;
uint64_t Da, De, Di, Do, Du;
uint64_t Eba, Ebe, Ebi, Ebo, Ebu;
uint64_t Ega, Ege, Egi, Ego, Egu;
uint64_t Eka, Eke, Eki, Eko, Eku;
uint64_t Ema, Eme, Emi, Emo, Emu;
uint64_t Esa, Ese, Esi, Eso, Esu;
//copyFromState(A, state)
Aba = state[ 0];
Abe = state[ 1];
Abi = state[ 2];
Abo = state[ 3];
Abu = state[ 4];
Aga = state[ 5];
Age = state[ 6];
Agi = state[ 7];
Ago = state[ 8];
Agu = state[ 9];
Aka = state[10];
Ake = state[11];
Aki = state[12];
Ako = state[13];
Aku = state[14];
Ama = state[15];
Ame = state[16];
Ami = state[17];
Amo = state[18];
Amu = state[19];
Asa = state[20];
Ase = state[21];
Asi = state[22];
Aso = state[23];
Asu = state[24];
for(round = 0; round < NROUNDS; round += 2) {
// prepareTheta
BCa = Aba^Aga^Aka^Ama^Asa;
BCe = Abe^Age^Ake^Ame^Ase;
BCi = Abi^Agi^Aki^Ami^Asi;
BCo = Abo^Ago^Ako^Amo^Aso;
BCu = Abu^Agu^Aku^Amu^Asu;
//thetaRhoPiChiIotaPrepareTheta(round, A, E)
Da = BCu^ROL(BCe, 1);
De = BCa^ROL(BCi, 1);
Di = BCe^ROL(BCo, 1);
Do = BCi^ROL(BCu, 1);
Du = BCo^ROL(BCa, 1);
Aba ^= Da;
BCa = Aba;
Age ^= De;
BCe = ROL(Age, 44);
Aki ^= Di;
BCi = ROL(Aki, 43);
Amo ^= Do;
BCo = ROL(Amo, 21);
Asu ^= Du;
BCu = ROL(Asu, 14);
Eba = BCa ^((~BCe)& BCi );
Eba ^= (uint64_t)KeccakF_RoundConstants[round];
Ebe = BCe ^((~BCi)& BCo );
Ebi = BCi ^((~BCo)& BCu );
Ebo = BCo ^((~BCu)& BCa );
Ebu = BCu ^((~BCa)& BCe );
Abo ^= Do;
BCa = ROL(Abo, 28);
Agu ^= Du;
BCe = ROL(Agu, 20);
Aka ^= Da;
BCi = ROL(Aka, 3);
Ame ^= De;
BCo = ROL(Ame, 45);
Asi ^= Di;
BCu = ROL(Asi, 61);
Ega = BCa ^((~BCe)& BCi );
Ege = BCe ^((~BCi)& BCo );
Egi = BCi ^((~BCo)& BCu );
Ego = BCo ^((~BCu)& BCa );
Egu = BCu ^((~BCa)& BCe );
Abe ^= De;
BCa = ROL(Abe, 1);
Agi ^= Di;
BCe = ROL(Agi, 6);
Ako ^= Do;
BCi = ROL(Ako, 25);
Amu ^= Du;
BCo = ROL(Amu, 8);
Asa ^= Da;
BCu = ROL(Asa, 18);
Eka = BCa ^((~BCe)& BCi );
Eke = BCe ^((~BCi)& BCo );
Eki = BCi ^((~BCo)& BCu );
Eko = BCo ^((~BCu)& BCa );
Eku = BCu ^((~BCa)& BCe );
Abu ^= Du;
BCa = ROL(Abu, 27);
Aga ^= Da;
BCe = ROL(Aga, 36);
Ake ^= De;
BCi = ROL(Ake, 10);
Ami ^= Di;
BCo = ROL(Ami, 15);
Aso ^= Do;
BCu = ROL(Aso, 56);
Ema = BCa ^((~BCe)& BCi );
Eme = BCe ^((~BCi)& BCo );
Emi = BCi ^((~BCo)& BCu );
Emo = BCo ^((~BCu)& BCa );
Emu = BCu ^((~BCa)& BCe );
Abi ^= Di;
BCa = ROL(Abi, 62);
Ago ^= Do;
BCe = ROL(Ago, 55);
Aku ^= Du;
BCi = ROL(Aku, 39);
Ama ^= Da;
BCo = ROL(Ama, 41);
Ase ^= De;
BCu = ROL(Ase, 2);
Esa = BCa ^((~BCe)& BCi );
Ese = BCe ^((~BCi)& BCo );
Esi = BCi ^((~BCo)& BCu );
Eso = BCo ^((~BCu)& BCa );
Esu = BCu ^((~BCa)& BCe );
// prepareTheta
BCa = Eba^Ega^Eka^Ema^Esa;
BCe = Ebe^Ege^Eke^Eme^Ese;
BCi = Ebi^Egi^Eki^Emi^Esi;
BCo = Ebo^Ego^Eko^Emo^Eso;
BCu = Ebu^Egu^Eku^Emu^Esu;
//thetaRhoPiChiIotaPrepareTheta(round+1, E, A)
Da = BCu^ROL(BCe, 1);
De = BCa^ROL(BCi, 1);
Di = BCe^ROL(BCo, 1);
Do = BCi^ROL(BCu, 1);
Du = BCo^ROL(BCa, 1);
Eba ^= Da;
BCa = Eba;
Ege ^= De;
BCe = ROL(Ege, 44);
Eki ^= Di;
BCi = ROL(Eki, 43);
Emo ^= Do;
BCo = ROL(Emo, 21);
Esu ^= Du;
BCu = ROL(Esu, 14);
Aba = BCa ^((~BCe)& BCi );
Aba ^= (uint64_t)KeccakF_RoundConstants[round+1];
Abe = BCe ^((~BCi)& BCo );
Abi = BCi ^((~BCo)& BCu );
Abo = BCo ^((~BCu)& BCa );
Abu = BCu ^((~BCa)& BCe );
Ebo ^= Do;
BCa = ROL(Ebo, 28);
Egu ^= Du;
BCe = ROL(Egu, 20);
Eka ^= Da;
BCi = ROL(Eka, 3);
Eme ^= De;
BCo = ROL(Eme, 45);
Esi ^= Di;
BCu = ROL(Esi, 61);
Aga = BCa ^((~BCe)& BCi );
Age = BCe ^((~BCi)& BCo );
Agi = BCi ^((~BCo)& BCu );
Ago = BCo ^((~BCu)& BCa );
Agu = BCu ^((~BCa)& BCe );
Ebe ^= De;
BCa = ROL(Ebe, 1);
Egi ^= Di;
BCe = ROL(Egi, 6);
Eko ^= Do;
BCi = ROL(Eko, 25);
Emu ^= Du;
BCo = ROL(Emu, 8);
Esa ^= Da;
BCu = ROL(Esa, 18);
Aka = BCa ^((~BCe)& BCi );
Ake = BCe ^((~BCi)& BCo );
Aki = BCi ^((~BCo)& BCu );
Ako = BCo ^((~BCu)& BCa );
Aku = BCu ^((~BCa)& BCe );
Ebu ^= Du;
BCa = ROL(Ebu, 27);
Ega ^= Da;
BCe = ROL(Ega, 36);
Eke ^= De;
BCi = ROL(Eke, 10);
Emi ^= Di;
BCo = ROL(Emi, 15);
Eso ^= Do;
BCu = ROL(Eso, 56);
Ama = BCa ^((~BCe)& BCi );
Ame = BCe ^((~BCi)& BCo );
Ami = BCi ^((~BCo)& BCu );
Amo = BCo ^((~BCu)& BCa );
Amu = BCu ^((~BCa)& BCe );
Ebi ^= Di;
BCa = ROL(Ebi, 62);
Ego ^= Do;
BCe = ROL(Ego, 55);
Eku ^= Du;
BCi = ROL(Eku, 39);
Ema ^= Da;
BCo = ROL(Ema, 41);
Ese ^= De;
BCu = ROL(Ese, 2);
Asa = BCa ^((~BCe)& BCi );
Ase = BCe ^((~BCi)& BCo );
Asi = BCi ^((~BCo)& BCu );
Aso = BCo ^((~BCu)& BCa );
Asu = BCu ^((~BCa)& BCe );
}
//copyToState(state, A)
state[ 0] = Aba;
state[ 1] = Abe;
state[ 2] = Abi;
state[ 3] = Abo;
state[ 4] = Abu;
state[ 5] = Aga;
state[ 6] = Age;
state[ 7] = Agi;
state[ 8] = Ago;
state[ 9] = Agu;
state[10] = Aka;
state[11] = Ake;
state[12] = Aki;
state[13] = Ako;
state[14] = Aku;
state[15] = Ama;
state[16] = Ame;
state[17] = Ami;
state[18] = Amo;
state[19] = Amu;
state[20] = Asa;
state[21] = Ase;
state[22] = Asi;
state[23] = Aso;
state[24] = Asu;
}
/*************************************************
* Name: keccak_init
*
* Description: Initializes the Keccak state.
*
* Arguments: - uint64_t *s: pointer to Keccak state
**************************************************/
static void keccak_init(uint64_t s[25])
{
unsigned int i;
for(i=0;i<25;i++)
s[i] = 0;
}
/*************************************************
* Name: keccak_absorb
*
* Description: Absorb step of Keccak; incremental.
*
* Arguments: - uint64_t *s: pointer to Keccak state
* - unsigned int pos: position in current block to be absorbed
* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
* - const uint8_t *in: pointer to input to be absorbed into s
* - size_t inlen: length of input in bytes
*
* Returns new position pos in current block
**************************************************/
static unsigned int keccak_absorb(uint64_t s[25],
unsigned int pos,
unsigned int r,
const uint8_t *in,
size_t inlen)
{
unsigned int i;
while(pos+inlen >= r) {
for(i=pos;i<r;i++)
s[i/8] ^= (uint64_t)*in++ << 8*(i%8);
inlen -= r-pos;
KeccakF1600_StatePermute(s);
pos = 0;
}
for(i=pos;i<pos+inlen;i++)
s[i/8] ^= (uint64_t)*in++ << 8*(i%8);
return i;
}
/*************************************************
* Name: keccak_finalize
*
* Description: Finalize absorb step.
*
* Arguments: - uint64_t *s: pointer to Keccak state
* - unsigned int pos: position in current block to be absorbed
* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
* - uint8_t p: domain separation byte
**************************************************/
static void keccak_finalize(uint64_t s[25], unsigned int pos, unsigned int r, uint8_t p)
{
s[pos/8] ^= (uint64_t)p << 8*(pos%8);
s[r/8-1] ^= 1ULL << 63;
}
/*************************************************
* Name: keccak_squeeze
*
* Description: Squeeze step of Keccak. Squeezes arbitratrily many bytes.
* Modifies the state. Can be called multiple times to keep
* squeezing, i.e., is incremental.
*
* Arguments: - uint8_t *out: pointer to output
* - size_t outlen: number of bytes to be squeezed (written to out)
* - uint64_t *s: pointer to input/output Keccak state
* - unsigned int pos: number of bytes in current block already squeezed
* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
*
* Returns new position pos in current block
**************************************************/
static unsigned int keccak_squeeze(uint8_t *out,
size_t outlen,
uint64_t s[25],
unsigned int pos,
unsigned int r)
{
unsigned int i;
while(outlen) {
if(pos == r) {
KeccakF1600_StatePermute(s);
pos = 0;
}
for(i=pos;i < r && i < pos+outlen; i++)
*out++ = s[i/8] >> 8*(i%8);
outlen -= i-pos;
pos = i;
}
return pos;
}
/*************************************************
* Name: keccak_absorb_once
*
* Description: Absorb step of Keccak;
* non-incremental, starts by zeroeing the state.
*
* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state
* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
* - const uint8_t *in: pointer to input to be absorbed into s
* - size_t inlen: length of input in bytes
* - uint8_t p: domain-separation byte for different Keccak-derived functions
**************************************************/
static void keccak_absorb_once(uint64_t s[25],
unsigned int r,
const uint8_t *in,
size_t inlen,
uint8_t p)
{
unsigned int i;
for(i=0;i<25;i++)
s[i] = 0;
while(inlen >= r) {
for(i=0;i<r/8;i++)
s[i] ^= load64(in+8*i);
in += r;
inlen -= r;
KeccakF1600_StatePermute(s);
}
for(i=0;i<inlen;i++)
s[i/8] ^= (uint64_t)in[i] << 8*(i%8);
s[i/8] ^= (uint64_t)p << 8*(i%8);
s[(r-1)/8] ^= 1ULL << 63;
}
/*************************************************
* Name: keccak_squeezeblocks
*
* Description: Squeeze step of Keccak. Squeezes full blocks of r bytes each.
* Modifies the state. Can be called multiple times to keep
* squeezing, i.e., is incremental. Assumes zero bytes of current
* block have already been squeezed.
*
* Arguments: - uint8_t *out: pointer to output blocks
* - size_t nblocks: number of blocks to be squeezed (written to out)
* - uint64_t *s: pointer to input/output Keccak state
* - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
**************************************************/
static void keccak_squeezeblocks(uint8_t *out,
size_t nblocks,
uint64_t s[25],
unsigned int r)
{
unsigned int i;
while(nblocks) {
KeccakF1600_StatePermute(s);
for(i=0;i<r/8;i++)
store64(out+8*i, s[i]);
out += r;
nblocks -= 1;
}
}
/*************************************************
* Name: shake128_init
*
* Description: Initilizes Keccak state for use as SHAKE128 XOF
*
* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state
**************************************************/
void shake128_init(keccak_state *state)
{
keccak_init(state->s);
state->pos = 0;
}
/*************************************************
* Name: shake128_absorb
*
* Description: Absorb step of the SHAKE128 XOF; incremental.
*
* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state
* - const uint8_t *in: pointer to input to be absorbed into s
* - size_t inlen: length of input in bytes
**************************************************/
void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen)
{
state->pos = keccak_absorb(state->s, state->pos, SHAKE128_RATE, in, inlen);
}
/*************************************************
* Name: shake128_finalize
*
* Description: Finalize absorb step of the SHAKE128 XOF.
*
* Arguments: - keccak_state *state: pointer to Keccak state
**************************************************/
void shake128_finalize(keccak_state *state)
{
keccak_finalize(state->s, state->pos, SHAKE128_RATE, 0x1F);
state->pos = SHAKE128_RATE;
}
/*************************************************
* Name: shake128_squeeze
*
* Description: Squeeze step of SHAKE128 XOF. Squeezes arbitraily many
* bytes. Can be called multiple times to keep squeezing.
*
* Arguments: - uint8_t *out: pointer to output blocks
* - size_t outlen : number of bytes to be squeezed (written to output)
* - keccak_state *s: pointer to input/output Keccak state
**************************************************/
void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state)
{
state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE128_RATE);
}
/*************************************************
* Name: shake128_absorb_once
*
* Description: Initialize, absorb into and finalize SHAKE128 XOF; non-incremental.
*
* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state
* - const uint8_t *in: pointer to input to be absorbed into s
* - size_t inlen: length of input in bytes
**************************************************/
void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen)
{
keccak_absorb_once(state->s, SHAKE128_RATE, in, inlen, 0x1F);
state->pos = SHAKE128_RATE;
}
/*************************************************
* Name: shake128_squeezeblocks
*
* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of
* SHAKE128_RATE bytes each. Can be called multiple times
* to keep squeezing. Assumes new block has not yet been
* started (state->pos = SHAKE128_RATE).
*
* Arguments: - uint8_t *out: pointer to output blocks
* - size_t nblocks: number of blocks to be squeezed (written to output)
* - keccak_state *s: pointer to input/output Keccak state
**************************************************/
void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state)
{
keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE);
}
/*************************************************
* Name: shake256_init
*
* Description: Initilizes Keccak state for use as SHAKE256 XOF
*
* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state
**************************************************/
void shake256_init(keccak_state *state)
{
keccak_init(state->s);
state->pos = 0;
}
/*************************************************
* Name: shake256_absorb
*
* Description: Absorb step of the SHAKE256 XOF; incremental.
*
* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state
* - const uint8_t *in: pointer to input to be absorbed into s
* - size_t inlen: length of input in bytes
**************************************************/
void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen)
{
state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen);
}
/*************************************************
* Name: shake256_finalize
*
* Description: Finalize absorb step of the SHAKE256 XOF.
*
* Arguments: - keccak_state *state: pointer to Keccak state
**************************************************/
void shake256_finalize(keccak_state *state)
{
keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F);
state->pos = SHAKE256_RATE;
}
/*************************************************
* Name: shake256_squeeze
*
* Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many
* bytes. Can be called multiple times to keep squeezing.
*
* Arguments: - uint8_t *out: pointer to output blocks
* - size_t outlen : number of bytes to be squeezed (written to output)
* - keccak_state *s: pointer to input/output Keccak state
**************************************************/
void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state)
{
state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE);
}
/*************************************************
* Name: shake256_absorb_once
*
* Description: Initialize, absorb into and finalize SHAKE256 XOF; non-incremental.
*
* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state
* - const uint8_t *in: pointer to input to be absorbed into s
* - size_t inlen: length of input in bytes
**************************************************/
void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen)
{
keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F);
state->pos = SHAKE256_RATE;
}
/*************************************************
* Name: shake256_squeezeblocks
*
* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of
* SHAKE256_RATE bytes each. Can be called multiple times
* to keep squeezing. Assumes next block has not yet been
* started (state->pos = SHAKE256_RATE).
*
* Arguments: - uint8_t *out: pointer to output blocks
* - size_t nblocks: number of blocks to be squeezed (written to output)
* - keccak_state *s: pointer to input/output Keccak state
**************************************************/
void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state)
{
keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE);
}
/*************************************************
* Name: shake128
*
* Description: SHAKE128 XOF with non-incremental API
*
* Arguments: - uint8_t *out: pointer to output
* - size_t outlen: requested output length in bytes
* - const uint8_t *in: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen)
{
size_t nblocks;
keccak_state state;
shake128_absorb_once(&state, in, inlen);
nblocks = outlen/SHAKE128_RATE;
shake128_squeezeblocks(out, nblocks, &state);
outlen -= nblocks*SHAKE128_RATE;
out += nblocks*SHAKE128_RATE;
shake128_squeeze(out, outlen, &state);
}
/*************************************************
* Name: shake256
*
* Description: SHAKE256 XOF with non-incremental API
*
* Arguments: - uint8_t *out: pointer to output
* - size_t outlen: requested output length in bytes
* - const uint8_t *in: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen)
{
size_t nblocks;
keccak_state state;
shake256_absorb_once(&state, in, inlen);
nblocks = outlen/SHAKE256_RATE;
shake256_squeezeblocks(out, nblocks, &state);
outlen -= nblocks*SHAKE256_RATE;
out += nblocks*SHAKE256_RATE;
shake256_squeeze(out, outlen, &state);
}
/*************************************************
* Name: sha3_256
*
* Description: SHA3-256 with non-incremental API
*
* Arguments: - uint8_t *h: pointer to output (32 bytes)
* - const uint8_t *in: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen)
{
unsigned int i;
uint64_t s[25];
keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06);
KeccakF1600_StatePermute(s);
for(i=0;i<4;i++)
store64(h+8*i,s[i]);
}
/*************************************************
* Name: sha3_512
*
* Description: SHA3-512 with non-incremental API
*
* Arguments: - uint8_t *h: pointer to output (64 bytes)
* - const uint8_t *in: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen)
{
unsigned int i;
uint64_t s[25];
keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06);
KeccakF1600_StatePermute(s);
for(i=0;i<8;i++)
store64(h+8*i,s[i]);
}

View file

@ -0,0 +1,57 @@
#ifndef FIPS202_H
#define FIPS202_H
#include <stddef.h>
#include <stdint.h>
#define SHAKE128_RATE 168
#define SHAKE256_RATE 136
#define SHA3_256_RATE 136
#define SHA3_512_RATE 72
#define FIPS202_NAMESPACE(s) pqcrystals_dilithium_fips202_ref_##s
typedef struct {
uint64_t s[25];
unsigned int pos;
} keccak_state;
#define KeccakF_RoundConstants FIPS202_NAMESPACE(KeccakF_RoundConstants)
extern const uint64_t KeccakF_RoundConstants[];
#define shake128_init FIPS202_NAMESPACE(shake128_init)
void shake128_init(keccak_state *state);
#define shake128_absorb FIPS202_NAMESPACE(shake128_absorb)
void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen);
#define shake128_finalize FIPS202_NAMESPACE(shake128_finalize)
void shake128_finalize(keccak_state *state);
#define shake128_squeeze FIPS202_NAMESPACE(shake128_squeeze)
void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state);
#define shake128_absorb_once FIPS202_NAMESPACE(shake128_absorb_once)
void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen);
#define shake128_squeezeblocks FIPS202_NAMESPACE(shake128_squeezeblocks)
void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state);
#define shake256_init FIPS202_NAMESPACE(shake256_init)
void shake256_init(keccak_state *state);
#define shake256_absorb FIPS202_NAMESPACE(shake256_absorb)
void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen);
#define shake256_finalize FIPS202_NAMESPACE(shake256_finalize)
void shake256_finalize(keccak_state *state);
#define shake256_squeeze FIPS202_NAMESPACE(shake256_squeeze)
void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state);
#define shake256_absorb_once FIPS202_NAMESPACE(shake256_absorb_once)
void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen);
#define shake256_squeezeblocks FIPS202_NAMESPACE(shake256_squeezeblocks)
void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state);
#define shake128 FIPS202_NAMESPACE(shake128)
void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen);
#define shake256 FIPS202_NAMESPACE(shake256)
void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen);
#define sha3_256 FIPS202_NAMESPACE(sha3_256)
void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen);
#define sha3_512 FIPS202_NAMESPACE(sha3_512)
void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen);
#endif

View file

@ -0,0 +1,98 @@
#include <stdint.h>
#include "params.h"
#include "ntt.h"
#include "reduce.h"
static const int32_t zetas[N] = {
0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468,
1826347, 2353451, -359251, -2091905, 3119733, -2884855, 3111497, 2680103,
2725464, 1024112, -1079900, 3585928, -549488, -1119584, 2619752, -2108549,
-2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, 280005,
2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439,
-3861115, -3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299,
-1699267, -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, 3699596,
811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779,
-3930395, -1528703, -3677745, -3041255, -1452451, 3475950, 2176455, -1585221,
-1257611, 1939314, -4083598, -1000202, -3190144, -3157330, -3632928, 126922,
3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047,
-671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430,
-3343383, 264944, 508951, 3097992, 44288, -1100098, 904516, 3958618,
-3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856,
189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330,
1285669, -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961,
2091667, 3407706, 2316500, 3817976, -3342478, 2244091, -2446433, -3562462,
266997, 2434439, -1235728, 3513181, -3520352, -3759364, -1197226, -3193378,
900702, 1859098, 909542, 819034, 495491, -1613174, -43260, -522500,
-655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838,
342297, 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044,
2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974,
-3767016, 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970,
-1333058, 1237275, -3318210, -1430225, -451100, 1312455, 3306115, -1962642,
-1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031,
-542412, -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993,
-2013608, 2432395, 2454455, -164721, 1957272, 3369112, 185531, -1207385,
-3183426, 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107,
-3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, 472078,
-426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893,
-2939036, -2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687,
-554416, 3919660, -48306, -1362209, 3937738, 1400424, -846154, 1976782
};
/*************************************************
* Name: ntt
*
* Description: Forward NTT, in-place. No modular reduction is performed after
* additions or subtractions. Output vector is in bitreversed order.
*
* Arguments: - uint32_t p[N]: input/output coefficient array
**************************************************/
void ntt(int32_t a[N]) {
unsigned int len, start, j, k;
int32_t zeta, t;
k = 0;
for(len = 128; len > 0; len >>= 1) {
for(start = 0; start < N; start = j + len) {
zeta = zetas[++k];
for(j = start; j < start + len; ++j) {
t = montgomery_reduce((int64_t)zeta * a[j + len]);
a[j + len] = a[j] - t;
a[j] = a[j] + t;
}
}
}
}
/*************************************************
* Name: invntt_tomont
*
* Description: Inverse NTT and multiplication by Montgomery factor 2^32.
* In-place. No modular reductions after additions or
* subtractions; input coefficients need to be smaller than
* Q in absolute value. Output coefficient are smaller than Q in
* absolute value.
*
* Arguments: - uint32_t p[N]: input/output coefficient array
**************************************************/
void invntt_tomont(int32_t a[N]) {
unsigned int start, len, j, k;
int32_t t, zeta;
const int32_t f = 41978; // mont^2/256
k = 256;
for(len = 1; len < N; len <<= 1) {
for(start = 0; start < N; start = j + len) {
zeta = -zetas[--k];
for(j = start; j < start + len; ++j) {
t = a[j];
a[j] = t + a[j + len];
a[j + len] = t - a[j + len];
a[j + len] = montgomery_reduce((int64_t)zeta * a[j + len]);
}
}
}
for(j = 0; j < N; ++j) {
a[j] = montgomery_reduce((int64_t)f * a[j]);
}
}

View file

@ -0,0 +1,13 @@
#ifndef NTT_H
#define NTT_H
#include <stdint.h>
#include "params.h"
#define ntt DILITHIUM_NAMESPACE(ntt)
void ntt(int32_t a[N]);
#define invntt_tomont DILITHIUM_NAMESPACE(invntt_tomont)
void invntt_tomont(int32_t a[N]);
#endif

View file

@ -0,0 +1,237 @@
#include "params.h"
#include "packing.h"
#include "polyvec.h"
#include "poly.h"
/*************************************************
* Name: pack_pk
*
* Description: Bit-pack public key pk = (rho, t1).
*
* Arguments: - uint8_t pk[]: output byte array
* - const uint8_t rho[]: byte array containing rho
* - const polyveck *t1: pointer to vector t1
**************************************************/
void pack_pk(uint8_t pk[CRYPTO_PUBLICKEYBYTES],
const uint8_t rho[SEEDBYTES],
const polyveck *t1)
{
unsigned int i;
for(i = 0; i < SEEDBYTES; ++i)
pk[i] = rho[i];
pk += SEEDBYTES;
for(i = 0; i < K; ++i)
polyt1_pack(pk + i*POLYT1_PACKEDBYTES, &t1->vec[i]);
}
/*************************************************
* Name: unpack_pk
*
* Description: Unpack public key pk = (rho, t1).
*
* Arguments: - const uint8_t rho[]: output byte array for rho
* - const polyveck *t1: pointer to output vector t1
* - uint8_t pk[]: byte array containing bit-packed pk
**************************************************/
void unpack_pk(uint8_t rho[SEEDBYTES],
polyveck *t1,
const uint8_t pk[CRYPTO_PUBLICKEYBYTES])
{
unsigned int i;
for(i = 0; i < SEEDBYTES; ++i)
rho[i] = pk[i];
pk += SEEDBYTES;
for(i = 0; i < K; ++i)
polyt1_unpack(&t1->vec[i], pk + i*POLYT1_PACKEDBYTES);
}
/*************************************************
* Name: pack_sk
*
* Description: Bit-pack secret key sk = (rho, tr, key, t0, s1, s2).
*
* Arguments: - uint8_t sk[]: output byte array
* - const uint8_t rho[]: byte array containing rho
* - const uint8_t tr[]: byte array containing tr
* - const uint8_t key[]: byte array containing key
* - const polyveck *t0: pointer to vector t0
* - const polyvecl *s1: pointer to vector s1
* - const polyveck *s2: pointer to vector s2
**************************************************/
void pack_sk(uint8_t sk[CRYPTO_SECRETKEYBYTES],
const uint8_t rho[SEEDBYTES],
const uint8_t tr[TRBYTES],
const uint8_t key[SEEDBYTES],
const polyveck *t0,
const polyvecl *s1,
const polyveck *s2)
{
unsigned int i;
for(i = 0; i < SEEDBYTES; ++i)
sk[i] = rho[i];
sk += SEEDBYTES;
for(i = 0; i < SEEDBYTES; ++i)
sk[i] = key[i];
sk += SEEDBYTES;
for(i = 0; i < TRBYTES; ++i)
sk[i] = tr[i];
sk += TRBYTES;
for(i = 0; i < L; ++i)
polyeta_pack(sk + i*POLYETA_PACKEDBYTES, &s1->vec[i]);
sk += L*POLYETA_PACKEDBYTES;
for(i = 0; i < K; ++i)
polyeta_pack(sk + i*POLYETA_PACKEDBYTES, &s2->vec[i]);
sk += K*POLYETA_PACKEDBYTES;
for(i = 0; i < K; ++i)
polyt0_pack(sk + i*POLYT0_PACKEDBYTES, &t0->vec[i]);
}
/*************************************************
* Name: unpack_sk
*
* Description: Unpack secret key sk = (rho, tr, key, t0, s1, s2).
*
* Arguments: - const uint8_t rho[]: output byte array for rho
* - const uint8_t tr[]: output byte array for tr
* - const uint8_t key[]: output byte array for key
* - const polyveck *t0: pointer to output vector t0
* - const polyvecl *s1: pointer to output vector s1
* - const polyveck *s2: pointer to output vector s2
* - uint8_t sk[]: byte array containing bit-packed sk
**************************************************/
void unpack_sk(uint8_t rho[SEEDBYTES],
uint8_t tr[TRBYTES],
uint8_t key[SEEDBYTES],
polyveck *t0,
polyvecl *s1,
polyveck *s2,
const uint8_t sk[CRYPTO_SECRETKEYBYTES])
{
unsigned int i;
for(i = 0; i < SEEDBYTES; ++i)
rho[i] = sk[i];
sk += SEEDBYTES;
for(i = 0; i < SEEDBYTES; ++i)
key[i] = sk[i];
sk += SEEDBYTES;
for(i = 0; i < TRBYTES; ++i)
tr[i] = sk[i];
sk += TRBYTES;
for(i=0; i < L; ++i)
polyeta_unpack(&s1->vec[i], sk + i*POLYETA_PACKEDBYTES);
sk += L*POLYETA_PACKEDBYTES;
for(i=0; i < K; ++i)
polyeta_unpack(&s2->vec[i], sk + i*POLYETA_PACKEDBYTES);
sk += K*POLYETA_PACKEDBYTES;
for(i=0; i < K; ++i)
polyt0_unpack(&t0->vec[i], sk + i*POLYT0_PACKEDBYTES);
}
/*************************************************
* Name: pack_sig
*
* Description: Bit-pack signature sig = (c, z, h).
*
* Arguments: - uint8_t sig[]: output byte array
* - const uint8_t *c: pointer to challenge hash length SEEDBYTES
* - const polyvecl *z: pointer to vector z
* - const polyveck *h: pointer to hint vector h
**************************************************/
void pack_sig(uint8_t sig[CRYPTO_BYTES],
const uint8_t c[CTILDEBYTES],
const polyvecl *z,
const polyveck *h)
{
unsigned int i, j, k;
for(i=0; i < CTILDEBYTES; ++i)
sig[i] = c[i];
sig += CTILDEBYTES;
for(i = 0; i < L; ++i)
polyz_pack(sig + i*POLYZ_PACKEDBYTES, &z->vec[i]);
sig += L*POLYZ_PACKEDBYTES;
/* Encode h */
for(i = 0; i < OMEGA + K; ++i)
sig[i] = 0;
k = 0;
for(i = 0; i < K; ++i) {
for(j = 0; j < N; ++j)
if(h->vec[i].coeffs[j] != 0)
sig[k++] = j;
sig[OMEGA + i] = k;
}
}
/*************************************************
* Name: unpack_sig
*
* Description: Unpack signature sig = (c, z, h).
*
* Arguments: - uint8_t *c: pointer to output challenge hash
* - polyvecl *z: pointer to output vector z
* - polyveck *h: pointer to output hint vector h
* - const uint8_t sig[]: byte array containing
* bit-packed signature
*
* Returns 1 in case of malformed signature; otherwise 0.
**************************************************/
int unpack_sig(uint8_t c[CTILDEBYTES],
polyvecl *z,
polyveck *h,
const uint8_t sig[CRYPTO_BYTES])
{
unsigned int i, j, k;
for(i = 0; i < CTILDEBYTES; ++i)
c[i] = sig[i];
sig += CTILDEBYTES;
for(i = 0; i < L; ++i)
polyz_unpack(&z->vec[i], sig + i*POLYZ_PACKEDBYTES);
sig += L*POLYZ_PACKEDBYTES;
/* Decode h */
k = 0;
for(i = 0; i < K; ++i) {
for(j = 0; j < N; ++j)
h->vec[i].coeffs[j] = 0;
if(sig[OMEGA + i] < k || sig[OMEGA + i] > OMEGA)
return 1;
for(j = k; j < sig[OMEGA + i]; ++j) {
/* Coefficients are ordered for strong unforgeability */
if(j > k && sig[j] <= sig[j-1]) return 1;
h->vec[i].coeffs[sig[j]] = 1;
}
k = sig[OMEGA + i];
}
/* Extra indices are zero for strong unforgeability */
for(j = k; j < OMEGA; ++j)
if(sig[j])
return 1;
return 0;
}

View file

@ -0,0 +1,38 @@
#ifndef PACKING_H
#define PACKING_H
#include <stdint.h>
#include "params.h"
#include "polyvec.h"
#define pack_pk DILITHIUM_NAMESPACE(pack_pk)
void pack_pk(uint8_t pk[CRYPTO_PUBLICKEYBYTES], const uint8_t rho[SEEDBYTES], const polyveck *t1);
#define pack_sk DILITHIUM_NAMESPACE(pack_sk)
void pack_sk(uint8_t sk[CRYPTO_SECRETKEYBYTES],
const uint8_t rho[SEEDBYTES],
const uint8_t tr[TRBYTES],
const uint8_t key[SEEDBYTES],
const polyveck *t0,
const polyvecl *s1,
const polyveck *s2);
#define pack_sig DILITHIUM_NAMESPACE(pack_sig)
void pack_sig(uint8_t sig[CRYPTO_BYTES], const uint8_t c[CTILDEBYTES], const polyvecl *z, const polyveck *h);
#define unpack_pk DILITHIUM_NAMESPACE(unpack_pk)
void unpack_pk(uint8_t rho[SEEDBYTES], polyveck *t1, const uint8_t pk[CRYPTO_PUBLICKEYBYTES]);
#define unpack_sk DILITHIUM_NAMESPACE(unpack_sk)
void unpack_sk(uint8_t rho[SEEDBYTES],
uint8_t tr[TRBYTES],
uint8_t key[SEEDBYTES],
polyveck *t0,
polyvecl *s1,
polyveck *s2,
const uint8_t sk[CRYPTO_SECRETKEYBYTES]);
#define unpack_sig DILITHIUM_NAMESPACE(unpack_sig)
int unpack_sig(uint8_t c[CTILDEBYTES], polyvecl *z, polyveck *h, const uint8_t sig[CRYPTO_BYTES]);
#endif

View file

@ -0,0 +1,80 @@
#ifndef PARAMS_H
#define PARAMS_H
#include "config.h"
#define SEEDBYTES 32
#define CRHBYTES 64
#define TRBYTES 64
#define RNDBYTES 32
#define N 256
#define Q 8380417
#define D 13
#define ROOT_OF_UNITY 1753
#if DILITHIUM_MODE == 2
#define K 4
#define L 4
#define ETA 2
#define TAU 39
#define BETA 78
#define GAMMA1 (1 << 17)
#define GAMMA2 ((Q-1)/88)
#define OMEGA 80
#define CTILDEBYTES 32
#elif DILITHIUM_MODE == 3
#define K 6
#define L 5
#define ETA 4
#define TAU 49
#define BETA 196
#define GAMMA1 (1 << 19)
#define GAMMA2 ((Q-1)/32)
#define OMEGA 55
#define CTILDEBYTES 48
#elif DILITHIUM_MODE == 5
#define K 8
#define L 7
#define ETA 2
#define TAU 60
#define BETA 120
#define GAMMA1 (1 << 19)
#define GAMMA2 ((Q-1)/32)
#define OMEGA 75
#define CTILDEBYTES 64
#endif
#define POLYT1_PACKEDBYTES 320
#define POLYT0_PACKEDBYTES 416
#define POLYVECH_PACKEDBYTES (OMEGA + K)
#if GAMMA1 == (1 << 17)
#define POLYZ_PACKEDBYTES 576
#elif GAMMA1 == (1 << 19)
#define POLYZ_PACKEDBYTES 640
#endif
#if GAMMA2 == (Q-1)/88
#define POLYW1_PACKEDBYTES 192
#elif GAMMA2 == (Q-1)/32
#define POLYW1_PACKEDBYTES 128
#endif
#if ETA == 2
#define POLYETA_PACKEDBYTES 96
#elif ETA == 4
#define POLYETA_PACKEDBYTES 128
#endif
#define CRYPTO_PUBLICKEYBYTES (SEEDBYTES + K*POLYT1_PACKEDBYTES)
#define CRYPTO_SECRETKEYBYTES (2*SEEDBYTES \
+ TRBYTES \
+ L*POLYETA_PACKEDBYTES \
+ K*POLYETA_PACKEDBYTES \
+ K*POLYT0_PACKEDBYTES)
#define CRYPTO_BYTES (CTILDEBYTES + L*POLYZ_PACKEDBYTES + POLYVECH_PACKEDBYTES)
#endif

View file

@ -0,0 +1,907 @@
#include <stdint.h>
#include "params.h"
#include "poly.h"
#include "ntt.h"
#include "reduce.h"
#include "rounding.h"
#include "symmetric.h"
#ifdef DBENCH
#include "test/cpucycles.h"
extern const uint64_t timing_overhead;
extern uint64_t *tred, *tadd, *tmul, *tround, *tsample, *tpack;
#define DBENCH_START() uint64_t time = cpucycles()
#define DBENCH_STOP(t) t += cpucycles() - time - timing_overhead
#else
#define DBENCH_START()
#define DBENCH_STOP(t)
#endif
/*************************************************
* Name: poly_reduce
*
* Description: Inplace reduction of all coefficients of polynomial to
* representative in [-6283008,6283008].
*
* Arguments: - poly *a: pointer to input/output polynomial
**************************************************/
void poly_reduce(poly *a) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
a->coeffs[i] = reduce32(a->coeffs[i]);
DBENCH_STOP(*tred);
}
/*************************************************
* Name: poly_caddq
*
* Description: For all coefficients of in/out polynomial add Q if
* coefficient is negative.
*
* Arguments: - poly *a: pointer to input/output polynomial
**************************************************/
void poly_caddq(poly *a) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
a->coeffs[i] = caddq(a->coeffs[i]);
DBENCH_STOP(*tred);
}
/*************************************************
* Name: poly_add
*
* Description: Add polynomials. No modular reduction is performed.
*
* Arguments: - poly *c: pointer to output polynomial
* - const poly *a: pointer to first summand
* - const poly *b: pointer to second summand
**************************************************/
void poly_add(poly *c, const poly *a, const poly *b) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
c->coeffs[i] = a->coeffs[i] + b->coeffs[i];
DBENCH_STOP(*tadd);
}
/*************************************************
* Name: poly_sub
*
* Description: Subtract polynomials. No modular reduction is
* performed.
*
* Arguments: - poly *c: pointer to output polynomial
* - const poly *a: pointer to first input polynomial
* - const poly *b: pointer to second input polynomial to be
* subtraced from first input polynomial
**************************************************/
void poly_sub(poly *c, const poly *a, const poly *b) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
c->coeffs[i] = a->coeffs[i] - b->coeffs[i];
DBENCH_STOP(*tadd);
}
/*************************************************
* Name: poly_shiftl
*
* Description: Multiply polynomial by 2^D without modular reduction. Assumes
* input coefficients to be less than 2^{31-D} in absolute value.
*
* Arguments: - poly *a: pointer to input/output polynomial
**************************************************/
void poly_shiftl(poly *a) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
a->coeffs[i] <<= D;
DBENCH_STOP(*tmul);
}
/*************************************************
* Name: poly_ntt
*
* Description: Inplace forward NTT. Coefficients can grow by
* 8*Q in absolute value.
*
* Arguments: - poly *a: pointer to input/output polynomial
**************************************************/
void poly_ntt(poly *a) {
DBENCH_START();
ntt(a->coeffs);
DBENCH_STOP(*tmul);
}
/*************************************************
* Name: poly_invntt_tomont
*
* Description: Inplace inverse NTT and multiplication by 2^{32}.
* Input coefficients need to be less than Q in absolute
* value and output coefficients are again bounded by Q.
*
* Arguments: - poly *a: pointer to input/output polynomial
**************************************************/
void poly_invntt_tomont(poly *a) {
DBENCH_START();
invntt_tomont(a->coeffs);
DBENCH_STOP(*tmul);
}
/*************************************************
* Name: poly_pointwise_montgomery
*
* Description: Pointwise multiplication of polynomials in NTT domain
* representation and multiplication of resulting polynomial
* by 2^{-32}.
*
* Arguments: - poly *c: pointer to output polynomial
* - const poly *a: pointer to first input polynomial
* - const poly *b: pointer to second input polynomial
**************************************************/
void poly_pointwise_montgomery(poly *c, const poly *a, const poly *b) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
c->coeffs[i] = montgomery_reduce((int64_t)a->coeffs[i] * b->coeffs[i]);
DBENCH_STOP(*tmul);
}
/*************************************************
* Name: poly_power2round
*
* Description: For all coefficients c of the input polynomial,
* compute c0, c1 such that c mod Q = c1*2^D + c0
* with -2^{D-1} < c0 <= 2^{D-1}. Assumes coefficients to be
* standard representatives.
*
* Arguments: - poly *a1: pointer to output polynomial with coefficients c1
* - poly *a0: pointer to output polynomial with coefficients c0
* - const poly *a: pointer to input polynomial
**************************************************/
void poly_power2round(poly *a1, poly *a0, const poly *a) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
a1->coeffs[i] = power2round(&a0->coeffs[i], a->coeffs[i]);
DBENCH_STOP(*tround);
}
/*************************************************
* Name: poly_decompose
*
* Description: For all coefficients c of the input polynomial,
* compute high and low bits c0, c1 such c mod Q = c1*ALPHA + c0
* with -ALPHA/2 < c0 <= ALPHA/2 except c1 = (Q-1)/ALPHA where we
* set c1 = 0 and -ALPHA/2 <= c0 = c mod Q - Q < 0.
* Assumes coefficients to be standard representatives.
*
* Arguments: - poly *a1: pointer to output polynomial with coefficients c1
* - poly *a0: pointer to output polynomial with coefficients c0
* - const poly *a: pointer to input polynomial
**************************************************/
void poly_decompose(poly *a1, poly *a0, const poly *a) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
a1->coeffs[i] = decompose(&a0->coeffs[i], a->coeffs[i]);
DBENCH_STOP(*tround);
}
/*************************************************
* Name: poly_make_hint
*
* Description: Compute hint polynomial. The coefficients of which indicate
* whether the low bits of the corresponding coefficient of
* the input polynomial overflow into the high bits.
*
* Arguments: - poly *h: pointer to output hint polynomial
* - const poly *a0: pointer to low part of input polynomial
* - const poly *a1: pointer to high part of input polynomial
*
* Returns number of 1 bits.
**************************************************/
unsigned int poly_make_hint(poly *h, const poly *a0, const poly *a1) {
unsigned int i, s = 0;
DBENCH_START();
for(i = 0; i < N; ++i) {
h->coeffs[i] = make_hint(a0->coeffs[i], a1->coeffs[i]);
s += h->coeffs[i];
}
DBENCH_STOP(*tround);
return s;
}
/*************************************************
* Name: poly_use_hint
*
* Description: Use hint polynomial to correct the high bits of a polynomial.
*
* Arguments: - poly *b: pointer to output polynomial with corrected high bits
* - const poly *a: pointer to input polynomial
* - const poly *h: pointer to input hint polynomial
**************************************************/
void poly_use_hint(poly *b, const poly *a, const poly *h) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N; ++i)
b->coeffs[i] = use_hint(a->coeffs[i], h->coeffs[i]);
DBENCH_STOP(*tround);
}
/*************************************************
* Name: poly_chknorm
*
* Description: Check infinity norm of polynomial against given bound.
* Assumes input coefficients were reduced by reduce32().
*
* Arguments: - const poly *a: pointer to polynomial
* - int32_t B: norm bound
*
* Returns 0 if norm is strictly smaller than B <= (Q-1)/8 and 1 otherwise.
**************************************************/
int poly_chknorm(const poly *a, int32_t B) {
unsigned int i;
int32_t t;
DBENCH_START();
if(B > (Q-1)/8)
return 1;
/* It is ok to leak which coefficient violates the bound since
the probability for each coefficient is independent of secret
data but we must not leak the sign of the centralized representative. */
for(i = 0; i < N; ++i) {
/* Absolute value */
t = a->coeffs[i] >> 31;
t = a->coeffs[i] - (t & 2*a->coeffs[i]);
if(t >= B) {
DBENCH_STOP(*tsample);
return 1;
}
}
DBENCH_STOP(*tsample);
return 0;
}
/*************************************************
* Name: rej_uniform
*
* Description: Sample uniformly random coefficients in [0, Q-1] by
* performing rejection sampling on array of random bytes.
*
* Arguments: - int32_t *a: pointer to output array (allocated)
* - unsigned int len: number of coefficients to be sampled
* - const uint8_t *buf: array of random bytes
* - unsigned int buflen: length of array of random bytes
*
* Returns number of sampled coefficients. Can be smaller than len if not enough
* random bytes were given.
**************************************************/
static unsigned int rej_uniform(int32_t *a,
unsigned int len,
const uint8_t *buf,
unsigned int buflen)
{
unsigned int ctr, pos;
uint32_t t;
DBENCH_START();
ctr = pos = 0;
while(ctr < len && pos + 3 <= buflen) {
t = buf[pos++];
t |= (uint32_t)buf[pos++] << 8;
t |= (uint32_t)buf[pos++] << 16;
t &= 0x7FFFFF;
if(t < Q)
a[ctr++] = t;
}
DBENCH_STOP(*tsample);
return ctr;
}
/*************************************************
* Name: poly_uniform
*
* Description: Sample polynomial with uniformly random coefficients
* in [0,Q-1] by performing rejection sampling on the
* output stream of SHAKE128(seed|nonce)
*
* Arguments: - poly *a: pointer to output polynomial
* - const uint8_t seed[]: byte array with seed of length SEEDBYTES
* - uint16_t nonce: 2-byte nonce
**************************************************/
#define POLY_UNIFORM_NBLOCKS ((768 + STREAM128_BLOCKBYTES - 1)/STREAM128_BLOCKBYTES)
void poly_uniform(poly *a,
const uint8_t seed[SEEDBYTES],
uint16_t nonce)
{
unsigned int i, ctr, off;
unsigned int buflen = POLY_UNIFORM_NBLOCKS*STREAM128_BLOCKBYTES;
uint8_t buf[POLY_UNIFORM_NBLOCKS*STREAM128_BLOCKBYTES + 2];
stream128_state state;
stream128_init(&state, seed, nonce);
stream128_squeezeblocks(buf, POLY_UNIFORM_NBLOCKS, &state);
ctr = rej_uniform(a->coeffs, N, buf, buflen);
while(ctr < N) {
off = buflen % 3;
for(i = 0; i < off; ++i)
buf[i] = buf[buflen - off + i];
stream128_squeezeblocks(buf + off, 1, &state);
buflen = STREAM128_BLOCKBYTES + off;
ctr += rej_uniform(a->coeffs + ctr, N - ctr, buf, buflen);
}
}
/*************************************************
* Name: rej_eta
*
* Description: Sample uniformly random coefficients in [-ETA, ETA] by
* performing rejection sampling on array of random bytes.
*
* Arguments: - int32_t *a: pointer to output array (allocated)
* - unsigned int len: number of coefficients to be sampled
* - const uint8_t *buf: array of random bytes
* - unsigned int buflen: length of array of random bytes
*
* Returns number of sampled coefficients. Can be smaller than len if not enough
* random bytes were given.
**************************************************/
static unsigned int rej_eta(int32_t *a,
unsigned int len,
const uint8_t *buf,
unsigned int buflen)
{
unsigned int ctr, pos;
uint32_t t0, t1;
DBENCH_START();
ctr = pos = 0;
while(ctr < len && pos < buflen) {
t0 = buf[pos] & 0x0F;
t1 = buf[pos++] >> 4;
#if ETA == 2
if(t0 < 15) {
t0 = t0 - (205*t0 >> 10)*5;
a[ctr++] = 2 - t0;
}
if(t1 < 15 && ctr < len) {
t1 = t1 - (205*t1 >> 10)*5;
a[ctr++] = 2 - t1;
}
#elif ETA == 4
if(t0 < 9)
a[ctr++] = 4 - t0;
if(t1 < 9 && ctr < len)
a[ctr++] = 4 - t1;
#endif
}
DBENCH_STOP(*tsample);
return ctr;
}
/*************************************************
* Name: poly_uniform_eta
*
* Description: Sample polynomial with uniformly random coefficients
* in [-ETA,ETA] by performing rejection sampling on the
* output stream from SHAKE256(seed|nonce)
*
* Arguments: - poly *a: pointer to output polynomial
* - const uint8_t seed[]: byte array with seed of length CRHBYTES
* - uint16_t nonce: 2-byte nonce
**************************************************/
#if ETA == 2
#define POLY_UNIFORM_ETA_NBLOCKS ((136 + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
#elif ETA == 4
#define POLY_UNIFORM_ETA_NBLOCKS ((227 + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
#endif
void poly_uniform_eta(poly *a,
const uint8_t seed[CRHBYTES],
uint16_t nonce)
{
unsigned int ctr;
unsigned int buflen = POLY_UNIFORM_ETA_NBLOCKS*STREAM256_BLOCKBYTES;
uint8_t buf[POLY_UNIFORM_ETA_NBLOCKS*STREAM256_BLOCKBYTES];
stream256_state state;
stream256_init(&state, seed, nonce);
stream256_squeezeblocks(buf, POLY_UNIFORM_ETA_NBLOCKS, &state);
ctr = rej_eta(a->coeffs, N, buf, buflen);
while(ctr < N) {
stream256_squeezeblocks(buf, 1, &state);
ctr += rej_eta(a->coeffs + ctr, N - ctr, buf, STREAM256_BLOCKBYTES);
}
}
/*************************************************
* Name: poly_uniform_gamma1m1
*
* Description: Sample polynomial with uniformly random coefficients
* in [-(GAMMA1 - 1), GAMMA1] by unpacking output stream
* of SHAKE256(seed|nonce)
*
* Arguments: - poly *a: pointer to output polynomial
* - const uint8_t seed[]: byte array with seed of length CRHBYTES
* - uint16_t nonce: 16-bit nonce
**************************************************/
#define POLY_UNIFORM_GAMMA1_NBLOCKS ((POLYZ_PACKEDBYTES + STREAM256_BLOCKBYTES - 1)/STREAM256_BLOCKBYTES)
void poly_uniform_gamma1(poly *a,
const uint8_t seed[CRHBYTES],
uint16_t nonce)
{
uint8_t buf[POLY_UNIFORM_GAMMA1_NBLOCKS*STREAM256_BLOCKBYTES];
stream256_state state;
stream256_init(&state, seed, nonce);
stream256_squeezeblocks(buf, POLY_UNIFORM_GAMMA1_NBLOCKS, &state);
polyz_unpack(a, buf);
}
/*************************************************
* Name: challenge
*
* Description: Implementation of H. Samples polynomial with TAU nonzero
* coefficients in {-1,1} using the output stream of
* SHAKE256(seed).
*
* Arguments: - poly *c: pointer to output polynomial
* - const uint8_t mu[]: byte array containing seed of length CTILDEBYTES
**************************************************/
void poly_challenge(poly *c, const uint8_t seed[CTILDEBYTES]) {
unsigned int i, b, pos;
uint64_t signs;
uint8_t buf[SHAKE256_RATE];
keccak_state state;
shake256_init(&state);
shake256_absorb(&state, seed, CTILDEBYTES);
shake256_finalize(&state);
shake256_squeezeblocks(buf, 1, &state);
signs = 0;
for(i = 0; i < 8; ++i)
signs |= (uint64_t)buf[i] << 8*i;
pos = 8;
for(i = 0; i < N; ++i)
c->coeffs[i] = 0;
for(i = N-TAU; i < N; ++i) {
do {
if(pos >= SHAKE256_RATE) {
shake256_squeezeblocks(buf, 1, &state);
pos = 0;
}
b = buf[pos++];
} while(b > i);
c->coeffs[i] = c->coeffs[b];
c->coeffs[b] = 1 - 2*(signs & 1);
signs >>= 1;
}
}
/*************************************************
* Name: polyeta_pack
*
* Description: Bit-pack polynomial with coefficients in [-ETA,ETA].
*
* Arguments: - uint8_t *r: pointer to output byte array with at least
* POLYETA_PACKEDBYTES bytes
* - const poly *a: pointer to input polynomial
**************************************************/
void polyeta_pack(uint8_t *r, const poly *a) {
unsigned int i;
uint8_t t[8];
DBENCH_START();
#if ETA == 2
for(i = 0; i < N/8; ++i) {
t[0] = ETA - a->coeffs[8*i+0];
t[1] = ETA - a->coeffs[8*i+1];
t[2] = ETA - a->coeffs[8*i+2];
t[3] = ETA - a->coeffs[8*i+3];
t[4] = ETA - a->coeffs[8*i+4];
t[5] = ETA - a->coeffs[8*i+5];
t[6] = ETA - a->coeffs[8*i+6];
t[7] = ETA - a->coeffs[8*i+7];
r[3*i+0] = (t[0] >> 0) | (t[1] << 3) | (t[2] << 6);
r[3*i+1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7);
r[3*i+2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5);
}
#elif ETA == 4
for(i = 0; i < N/2; ++i) {
t[0] = ETA - a->coeffs[2*i+0];
t[1] = ETA - a->coeffs[2*i+1];
r[i] = t[0] | (t[1] << 4);
}
#endif
DBENCH_STOP(*tpack);
}
/*************************************************
* Name: polyeta_unpack
*
* Description: Unpack polynomial with coefficients in [-ETA,ETA].
*
* Arguments: - poly *r: pointer to output polynomial
* - const uint8_t *a: byte array with bit-packed polynomial
**************************************************/
void polyeta_unpack(poly *r, const uint8_t *a) {
unsigned int i;
DBENCH_START();
#if ETA == 2
for(i = 0; i < N/8; ++i) {
r->coeffs[8*i+0] = (a[3*i+0] >> 0) & 7;
r->coeffs[8*i+1] = (a[3*i+0] >> 3) & 7;
r->coeffs[8*i+2] = ((a[3*i+0] >> 6) | (a[3*i+1] << 2)) & 7;
r->coeffs[8*i+3] = (a[3*i+1] >> 1) & 7;
r->coeffs[8*i+4] = (a[3*i+1] >> 4) & 7;
r->coeffs[8*i+5] = ((a[3*i+1] >> 7) | (a[3*i+2] << 1)) & 7;
r->coeffs[8*i+6] = (a[3*i+2] >> 2) & 7;
r->coeffs[8*i+7] = (a[3*i+2] >> 5) & 7;
r->coeffs[8*i+0] = ETA - r->coeffs[8*i+0];
r->coeffs[8*i+1] = ETA - r->coeffs[8*i+1];
r->coeffs[8*i+2] = ETA - r->coeffs[8*i+2];
r->coeffs[8*i+3] = ETA - r->coeffs[8*i+3];
r->coeffs[8*i+4] = ETA - r->coeffs[8*i+4];
r->coeffs[8*i+5] = ETA - r->coeffs[8*i+5];
r->coeffs[8*i+6] = ETA - r->coeffs[8*i+6];
r->coeffs[8*i+7] = ETA - r->coeffs[8*i+7];
}
#elif ETA == 4
for(i = 0; i < N/2; ++i) {
r->coeffs[2*i+0] = a[i] & 0x0F;
r->coeffs[2*i+1] = a[i] >> 4;
r->coeffs[2*i+0] = ETA - r->coeffs[2*i+0];
r->coeffs[2*i+1] = ETA - r->coeffs[2*i+1];
}
#endif
DBENCH_STOP(*tpack);
}
/*************************************************
* Name: polyt1_pack
*
* Description: Bit-pack polynomial t1 with coefficients fitting in 10 bits.
* Input coefficients are assumed to be standard representatives.
*
* Arguments: - uint8_t *r: pointer to output byte array with at least
* POLYT1_PACKEDBYTES bytes
* - const poly *a: pointer to input polynomial
**************************************************/
void polyt1_pack(uint8_t *r, const poly *a) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N/4; ++i) {
r[5*i+0] = (a->coeffs[4*i+0] >> 0);
r[5*i+1] = (a->coeffs[4*i+0] >> 8) | (a->coeffs[4*i+1] << 2);
r[5*i+2] = (a->coeffs[4*i+1] >> 6) | (a->coeffs[4*i+2] << 4);
r[5*i+3] = (a->coeffs[4*i+2] >> 4) | (a->coeffs[4*i+3] << 6);
r[5*i+4] = (a->coeffs[4*i+3] >> 2);
}
DBENCH_STOP(*tpack);
}
/*************************************************
* Name: polyt1_unpack
*
* Description: Unpack polynomial t1 with 10-bit coefficients.
* Output coefficients are standard representatives.
*
* Arguments: - poly *r: pointer to output polynomial
* - const uint8_t *a: byte array with bit-packed polynomial
**************************************************/
void polyt1_unpack(poly *r, const uint8_t *a) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N/4; ++i) {
r->coeffs[4*i+0] = ((a[5*i+0] >> 0) | ((uint32_t)a[5*i+1] << 8)) & 0x3FF;
r->coeffs[4*i+1] = ((a[5*i+1] >> 2) | ((uint32_t)a[5*i+2] << 6)) & 0x3FF;
r->coeffs[4*i+2] = ((a[5*i+2] >> 4) | ((uint32_t)a[5*i+3] << 4)) & 0x3FF;
r->coeffs[4*i+3] = ((a[5*i+3] >> 6) | ((uint32_t)a[5*i+4] << 2)) & 0x3FF;
}
DBENCH_STOP(*tpack);
}
/*************************************************
* Name: polyt0_pack
*
* Description: Bit-pack polynomial t0 with coefficients in ]-2^{D-1}, 2^{D-1}].
*
* Arguments: - uint8_t *r: pointer to output byte array with at least
* POLYT0_PACKEDBYTES bytes
* - const poly *a: pointer to input polynomial
**************************************************/
void polyt0_pack(uint8_t *r, const poly *a) {
unsigned int i;
uint32_t t[8];
DBENCH_START();
for(i = 0; i < N/8; ++i) {
t[0] = (1 << (D-1)) - a->coeffs[8*i+0];
t[1] = (1 << (D-1)) - a->coeffs[8*i+1];
t[2] = (1 << (D-1)) - a->coeffs[8*i+2];
t[3] = (1 << (D-1)) - a->coeffs[8*i+3];
t[4] = (1 << (D-1)) - a->coeffs[8*i+4];
t[5] = (1 << (D-1)) - a->coeffs[8*i+5];
t[6] = (1 << (D-1)) - a->coeffs[8*i+6];
t[7] = (1 << (D-1)) - a->coeffs[8*i+7];
r[13*i+ 0] = t[0];
r[13*i+ 1] = t[0] >> 8;
r[13*i+ 1] |= t[1] << 5;
r[13*i+ 2] = t[1] >> 3;
r[13*i+ 3] = t[1] >> 11;
r[13*i+ 3] |= t[2] << 2;
r[13*i+ 4] = t[2] >> 6;
r[13*i+ 4] |= t[3] << 7;
r[13*i+ 5] = t[3] >> 1;
r[13*i+ 6] = t[3] >> 9;
r[13*i+ 6] |= t[4] << 4;
r[13*i+ 7] = t[4] >> 4;
r[13*i+ 8] = t[4] >> 12;
r[13*i+ 8] |= t[5] << 1;
r[13*i+ 9] = t[5] >> 7;
r[13*i+ 9] |= t[6] << 6;
r[13*i+10] = t[6] >> 2;
r[13*i+11] = t[6] >> 10;
r[13*i+11] |= t[7] << 3;
r[13*i+12] = t[7] >> 5;
}
DBENCH_STOP(*tpack);
}
/*************************************************
* Name: polyt0_unpack
*
* Description: Unpack polynomial t0 with coefficients in ]-2^{D-1}, 2^{D-1}].
*
* Arguments: - poly *r: pointer to output polynomial
* - const uint8_t *a: byte array with bit-packed polynomial
**************************************************/
void polyt0_unpack(poly *r, const uint8_t *a) {
unsigned int i;
DBENCH_START();
for(i = 0; i < N/8; ++i) {
r->coeffs[8*i+0] = a[13*i+0];
r->coeffs[8*i+0] |= (uint32_t)a[13*i+1] << 8;
r->coeffs[8*i+0] &= 0x1FFF;
r->coeffs[8*i+1] = a[13*i+1] >> 5;
r->coeffs[8*i+1] |= (uint32_t)a[13*i+2] << 3;
r->coeffs[8*i+1] |= (uint32_t)a[13*i+3] << 11;
r->coeffs[8*i+1] &= 0x1FFF;
r->coeffs[8*i+2] = a[13*i+3] >> 2;
r->coeffs[8*i+2] |= (uint32_t)a[13*i+4] << 6;
r->coeffs[8*i+2] &= 0x1FFF;
r->coeffs[8*i+3] = a[13*i+4] >> 7;
r->coeffs[8*i+3] |= (uint32_t)a[13*i+5] << 1;
r->coeffs[8*i+3] |= (uint32_t)a[13*i+6] << 9;
r->coeffs[8*i+3] &= 0x1FFF;
r->coeffs[8*i+4] = a[13*i+6] >> 4;
r->coeffs[8*i+4] |= (uint32_t)a[13*i+7] << 4;
r->coeffs[8*i+4] |= (uint32_t)a[13*i+8] << 12;
r->coeffs[8*i+4] &= 0x1FFF;
r->coeffs[8*i+5] = a[13*i+8] >> 1;
r->coeffs[8*i+5] |= (uint32_t)a[13*i+9] << 7;
r->coeffs[8*i+5] &= 0x1FFF;
r->coeffs[8*i+6] = a[13*i+9] >> 6;
r->coeffs[8*i+6] |= (uint32_t)a[13*i+10] << 2;
r->coeffs[8*i+6] |= (uint32_t)a[13*i+11] << 10;
r->coeffs[8*i+6] &= 0x1FFF;
r->coeffs[8*i+7] = a[13*i+11] >> 3;
r->coeffs[8*i+7] |= (uint32_t)a[13*i+12] << 5;
r->coeffs[8*i+7] &= 0x1FFF;
r->coeffs[8*i+0] = (1 << (D-1)) - r->coeffs[8*i+0];
r->coeffs[8*i+1] = (1 << (D-1)) - r->coeffs[8*i+1];
r->coeffs[8*i+2] = (1 << (D-1)) - r->coeffs[8*i+2];
r->coeffs[8*i+3] = (1 << (D-1)) - r->coeffs[8*i+3];
r->coeffs[8*i+4] = (1 << (D-1)) - r->coeffs[8*i+4];
r->coeffs[8*i+5] = (1 << (D-1)) - r->coeffs[8*i+5];
r->coeffs[8*i+6] = (1 << (D-1)) - r->coeffs[8*i+6];
r->coeffs[8*i+7] = (1 << (D-1)) - r->coeffs[8*i+7];
}
DBENCH_STOP(*tpack);
}
/*************************************************
* Name: polyz_pack
*
* Description: Bit-pack polynomial with coefficients
* in [-(GAMMA1 - 1), GAMMA1].
*
* Arguments: - uint8_t *r: pointer to output byte array with at least
* POLYZ_PACKEDBYTES bytes
* - const poly *a: pointer to input polynomial
**************************************************/
void polyz_pack(uint8_t *r, const poly *a) {
unsigned int i;
uint32_t t[4];
DBENCH_START();
#if GAMMA1 == (1 << 17)
for(i = 0; i < N/4; ++i) {
t[0] = GAMMA1 - a->coeffs[4*i+0];
t[1] = GAMMA1 - a->coeffs[4*i+1];
t[2] = GAMMA1 - a->coeffs[4*i+2];
t[3] = GAMMA1 - a->coeffs[4*i+3];
r[9*i+0] = t[0];
r[9*i+1] = t[0] >> 8;
r[9*i+2] = t[0] >> 16;
r[9*i+2] |= t[1] << 2;
r[9*i+3] = t[1] >> 6;
r[9*i+4] = t[1] >> 14;
r[9*i+4] |= t[2] << 4;
r[9*i+5] = t[2] >> 4;
r[9*i+6] = t[2] >> 12;
r[9*i+6] |= t[3] << 6;
r[9*i+7] = t[3] >> 2;
r[9*i+8] = t[3] >> 10;
}
#elif GAMMA1 == (1 << 19)
for(i = 0; i < N/2; ++i) {
t[0] = GAMMA1 - a->coeffs[2*i+0];
t[1] = GAMMA1 - a->coeffs[2*i+1];
r[5*i+0] = t[0];
r[5*i+1] = t[0] >> 8;
r[5*i+2] = t[0] >> 16;
r[5*i+2] |= t[1] << 4;
r[5*i+3] = t[1] >> 4;
r[5*i+4] = t[1] >> 12;
}
#endif
DBENCH_STOP(*tpack);
}
/*************************************************
* Name: polyz_unpack
*
* Description: Unpack polynomial z with coefficients
* in [-(GAMMA1 - 1), GAMMA1].
*
* Arguments: - poly *r: pointer to output polynomial
* - const uint8_t *a: byte array with bit-packed polynomial
**************************************************/
void polyz_unpack(poly *r, const uint8_t *a) {
unsigned int i;
DBENCH_START();
#if GAMMA1 == (1 << 17)
for(i = 0; i < N/4; ++i) {
r->coeffs[4*i+0] = a[9*i+0];
r->coeffs[4*i+0] |= (uint32_t)a[9*i+1] << 8;
r->coeffs[4*i+0] |= (uint32_t)a[9*i+2] << 16;
r->coeffs[4*i+0] &= 0x3FFFF;
r->coeffs[4*i+1] = a[9*i+2] >> 2;
r->coeffs[4*i+1] |= (uint32_t)a[9*i+3] << 6;
r->coeffs[4*i+1] |= (uint32_t)a[9*i+4] << 14;
r->coeffs[4*i+1] &= 0x3FFFF;
r->coeffs[4*i+2] = a[9*i+4] >> 4;
r->coeffs[4*i+2] |= (uint32_t)a[9*i+5] << 4;
r->coeffs[4*i+2] |= (uint32_t)a[9*i+6] << 12;
r->coeffs[4*i+2] &= 0x3FFFF;
r->coeffs[4*i+3] = a[9*i+6] >> 6;
r->coeffs[4*i+3] |= (uint32_t)a[9*i+7] << 2;
r->coeffs[4*i+3] |= (uint32_t)a[9*i+8] << 10;
r->coeffs[4*i+3] &= 0x3FFFF;
r->coeffs[4*i+0] = GAMMA1 - r->coeffs[4*i+0];
r->coeffs[4*i+1] = GAMMA1 - r->coeffs[4*i+1];
r->coeffs[4*i+2] = GAMMA1 - r->coeffs[4*i+2];
r->coeffs[4*i+3] = GAMMA1 - r->coeffs[4*i+3];
}
#elif GAMMA1 == (1 << 19)
for(i = 0; i < N/2; ++i) {
r->coeffs[2*i+0] = a[5*i+0];
r->coeffs[2*i+0] |= (uint32_t)a[5*i+1] << 8;
r->coeffs[2*i+0] |= (uint32_t)a[5*i+2] << 16;
r->coeffs[2*i+0] &= 0xFFFFF;
r->coeffs[2*i+1] = a[5*i+2] >> 4;
r->coeffs[2*i+1] |= (uint32_t)a[5*i+3] << 4;
r->coeffs[2*i+1] |= (uint32_t)a[5*i+4] << 12;
/* r->coeffs[2*i+1] &= 0xFFFFF; */ /* No effect, since we're anyway at 20 bits */
r->coeffs[2*i+0] = GAMMA1 - r->coeffs[2*i+0];
r->coeffs[2*i+1] = GAMMA1 - r->coeffs[2*i+1];
}
#endif
DBENCH_STOP(*tpack);
}
/*************************************************
* Name: polyw1_pack
*
* Description: Bit-pack polynomial w1 with coefficients in [0,15] or [0,43].
* Input coefficients are assumed to be standard representatives.
*
* Arguments: - uint8_t *r: pointer to output byte array with at least
* POLYW1_PACKEDBYTES bytes
* - const poly *a: pointer to input polynomial
**************************************************/
void polyw1_pack(uint8_t *r, const poly *a) {
unsigned int i;
DBENCH_START();
#if GAMMA2 == (Q-1)/88
for(i = 0; i < N/4; ++i) {
r[3*i+0] = a->coeffs[4*i+0];
r[3*i+0] |= a->coeffs[4*i+1] << 6;
r[3*i+1] = a->coeffs[4*i+1] >> 2;
r[3*i+1] |= a->coeffs[4*i+2] << 4;
r[3*i+2] = a->coeffs[4*i+2] >> 4;
r[3*i+2] |= a->coeffs[4*i+3] << 2;
}
#elif GAMMA2 == (Q-1)/32
for(i = 0; i < N/2; ++i)
r[i] = a->coeffs[2*i+0] | (a->coeffs[2*i+1] << 4);
#endif
DBENCH_STOP(*tpack);
}

View file

@ -0,0 +1,79 @@
#ifndef POLY_H
#define POLY_H
#include <stdint.h>
#include "params.h"
typedef struct {
int32_t coeffs[N];
} poly;
#define poly_reduce DILITHIUM_NAMESPACE(poly_reduce)
void poly_reduce(poly *a);
#define poly_caddq DILITHIUM_NAMESPACE(poly_caddq)
void poly_caddq(poly *a);
#define poly_add DILITHIUM_NAMESPACE(poly_add)
void poly_add(poly *c, const poly *a, const poly *b);
#define poly_sub DILITHIUM_NAMESPACE(poly_sub)
void poly_sub(poly *c, const poly *a, const poly *b);
#define poly_shiftl DILITHIUM_NAMESPACE(poly_shiftl)
void poly_shiftl(poly *a);
#define poly_ntt DILITHIUM_NAMESPACE(poly_ntt)
void poly_ntt(poly *a);
#define poly_invntt_tomont DILITHIUM_NAMESPACE(poly_invntt_tomont)
void poly_invntt_tomont(poly *a);
#define poly_pointwise_montgomery DILITHIUM_NAMESPACE(poly_pointwise_montgomery)
void poly_pointwise_montgomery(poly *c, const poly *a, const poly *b);
#define poly_power2round DILITHIUM_NAMESPACE(poly_power2round)
void poly_power2round(poly *a1, poly *a0, const poly *a);
#define poly_decompose DILITHIUM_NAMESPACE(poly_decompose)
void poly_decompose(poly *a1, poly *a0, const poly *a);
#define poly_make_hint DILITHIUM_NAMESPACE(poly_make_hint)
unsigned int poly_make_hint(poly *h, const poly *a0, const poly *a1);
#define poly_use_hint DILITHIUM_NAMESPACE(poly_use_hint)
void poly_use_hint(poly *b, const poly *a, const poly *h);
#define poly_chknorm DILITHIUM_NAMESPACE(poly_chknorm)
int poly_chknorm(const poly *a, int32_t B);
#define poly_uniform DILITHIUM_NAMESPACE(poly_uniform)
void poly_uniform(poly *a,
const uint8_t seed[SEEDBYTES],
uint16_t nonce);
#define poly_uniform_eta DILITHIUM_NAMESPACE(poly_uniform_eta)
void poly_uniform_eta(poly *a,
const uint8_t seed[CRHBYTES],
uint16_t nonce);
#define poly_uniform_gamma1 DILITHIUM_NAMESPACE(poly_uniform_gamma1)
void poly_uniform_gamma1(poly *a,
const uint8_t seed[CRHBYTES],
uint16_t nonce);
#define poly_challenge DILITHIUM_NAMESPACE(poly_challenge)
void poly_challenge(poly *c, const uint8_t seed[CTILDEBYTES]);
#define polyeta_pack DILITHIUM_NAMESPACE(polyeta_pack)
void polyeta_pack(uint8_t *r, const poly *a);
#define polyeta_unpack DILITHIUM_NAMESPACE(polyeta_unpack)
void polyeta_unpack(poly *r, const uint8_t *a);
#define polyt1_pack DILITHIUM_NAMESPACE(polyt1_pack)
void polyt1_pack(uint8_t *r, const poly *a);
#define polyt1_unpack DILITHIUM_NAMESPACE(polyt1_unpack)
void polyt1_unpack(poly *r, const uint8_t *a);
#define polyt0_pack DILITHIUM_NAMESPACE(polyt0_pack)
void polyt0_pack(uint8_t *r, const poly *a);
#define polyt0_unpack DILITHIUM_NAMESPACE(polyt0_unpack)
void polyt0_unpack(poly *r, const uint8_t *a);
#define polyz_pack DILITHIUM_NAMESPACE(polyz_pack)
void polyz_pack(uint8_t *r, const poly *a);
#define polyz_unpack DILITHIUM_NAMESPACE(polyz_unpack)
void polyz_unpack(poly *r, const uint8_t *a);
#define polyw1_pack DILITHIUM_NAMESPACE(polyw1_pack)
void polyw1_pack(uint8_t *r, const poly *a);
#endif

View file

@ -0,0 +1,389 @@
#include <stdint.h>
#include "params.h"
#include "polyvec.h"
#include "poly.h"
/*************************************************
* Name: expand_mat
*
* Description: Implementation of ExpandA. Generates matrix A with uniformly
* random coefficients a_{i,j} by performing rejection
* sampling on the output stream of SHAKE128(rho|j|i)
*
* Arguments: - polyvecl mat[K]: output matrix
* - const uint8_t rho[]: byte array containing seed rho
**************************************************/
void polyvec_matrix_expand(polyvecl mat[K], const uint8_t rho[SEEDBYTES]) {
unsigned int i, j;
for(i = 0; i < K; ++i)
for(j = 0; j < L; ++j)
poly_uniform(&mat[i].vec[j], rho, (i << 8) + j);
}
void polyvec_matrix_pointwise_montgomery(polyveck *t, const polyvecl mat[K], const polyvecl *v) {
unsigned int i;
for(i = 0; i < K; ++i)
polyvecl_pointwise_acc_montgomery(&t->vec[i], &mat[i], v);
}
/**************************************************************/
/************ Vectors of polynomials of length L **************/
/**************************************************************/
void polyvecl_uniform_eta(polyvecl *v, const uint8_t seed[CRHBYTES], uint16_t nonce) {
unsigned int i;
for(i = 0; i < L; ++i)
poly_uniform_eta(&v->vec[i], seed, nonce++);
}
void polyvecl_uniform_gamma1(polyvecl *v, const uint8_t seed[CRHBYTES], uint16_t nonce) {
unsigned int i;
for(i = 0; i < L; ++i)
poly_uniform_gamma1(&v->vec[i], seed, L*nonce + i);
}
void polyvecl_reduce(polyvecl *v) {
unsigned int i;
for(i = 0; i < L; ++i)
poly_reduce(&v->vec[i]);
}
/*************************************************
* Name: polyvecl_add
*
* Description: Add vectors of polynomials of length L.
* No modular reduction is performed.
*
* Arguments: - polyvecl *w: pointer to output vector
* - const polyvecl *u: pointer to first summand
* - const polyvecl *v: pointer to second summand
**************************************************/
void polyvecl_add(polyvecl *w, const polyvecl *u, const polyvecl *v) {
unsigned int i;
for(i = 0; i < L; ++i)
poly_add(&w->vec[i], &u->vec[i], &v->vec[i]);
}
/*************************************************
* Name: polyvecl_ntt
*
* Description: Forward NTT of all polynomials in vector of length L. Output
* coefficients can be up to 16*Q larger than input coefficients.
*
* Arguments: - polyvecl *v: pointer to input/output vector
**************************************************/
void polyvecl_ntt(polyvecl *v) {
unsigned int i;
for(i = 0; i < L; ++i)
poly_ntt(&v->vec[i]);
}
void polyvecl_invntt_tomont(polyvecl *v) {
unsigned int i;
for(i = 0; i < L; ++i)
poly_invntt_tomont(&v->vec[i]);
}
void polyvecl_pointwise_poly_montgomery(polyvecl *r, const poly *a, const polyvecl *v) {
unsigned int i;
for(i = 0; i < L; ++i)
poly_pointwise_montgomery(&r->vec[i], a, &v->vec[i]);
}
/*************************************************
* Name: polyvecl_pointwise_acc_montgomery
*
* Description: Pointwise multiply vectors of polynomials of length L, multiply
* resulting vector by 2^{-32} and add (accumulate) polynomials
* in it. Input/output vectors are in NTT domain representation.
*
* Arguments: - poly *w: output polynomial
* - const polyvecl *u: pointer to first input vector
* - const polyvecl *v: pointer to second input vector
**************************************************/
void polyvecl_pointwise_acc_montgomery(poly *w,
const polyvecl *u,
const polyvecl *v)
{
unsigned int i;
poly t;
poly_pointwise_montgomery(w, &u->vec[0], &v->vec[0]);
for(i = 1; i < L; ++i) {
poly_pointwise_montgomery(&t, &u->vec[i], &v->vec[i]);
poly_add(w, w, &t);
}
}
/*************************************************
* Name: polyvecl_chknorm
*
* Description: Check infinity norm of polynomials in vector of length L.
* Assumes input polyvecl to be reduced by polyvecl_reduce().
*
* Arguments: - const polyvecl *v: pointer to vector
* - int32_t B: norm bound
*
* Returns 0 if norm of all polynomials is strictly smaller than B <= (Q-1)/8
* and 1 otherwise.
**************************************************/
int polyvecl_chknorm(const polyvecl *v, int32_t bound) {
unsigned int i;
for(i = 0; i < L; ++i)
if(poly_chknorm(&v->vec[i], bound))
return 1;
return 0;
}
/**************************************************************/
/************ Vectors of polynomials of length K **************/
/**************************************************************/
void polyveck_uniform_eta(polyveck *v, const uint8_t seed[CRHBYTES], uint16_t nonce) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_uniform_eta(&v->vec[i], seed, nonce++);
}
/*************************************************
* Name: polyveck_reduce
*
* Description: Reduce coefficients of polynomials in vector of length K
* to representatives in [-6283008,6283008].
*
* Arguments: - polyveck *v: pointer to input/output vector
**************************************************/
void polyveck_reduce(polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_reduce(&v->vec[i]);
}
/*************************************************
* Name: polyveck_caddq
*
* Description: For all coefficients of polynomials in vector of length K
* add Q if coefficient is negative.
*
* Arguments: - polyveck *v: pointer to input/output vector
**************************************************/
void polyveck_caddq(polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_caddq(&v->vec[i]);
}
/*************************************************
* Name: polyveck_add
*
* Description: Add vectors of polynomials of length K.
* No modular reduction is performed.
*
* Arguments: - polyveck *w: pointer to output vector
* - const polyveck *u: pointer to first summand
* - const polyveck *v: pointer to second summand
**************************************************/
void polyveck_add(polyveck *w, const polyveck *u, const polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_add(&w->vec[i], &u->vec[i], &v->vec[i]);
}
/*************************************************
* Name: polyveck_sub
*
* Description: Subtract vectors of polynomials of length K.
* No modular reduction is performed.
*
* Arguments: - polyveck *w: pointer to output vector
* - const polyveck *u: pointer to first input vector
* - const polyveck *v: pointer to second input vector to be
* subtracted from first input vector
**************************************************/
void polyveck_sub(polyveck *w, const polyveck *u, const polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_sub(&w->vec[i], &u->vec[i], &v->vec[i]);
}
/*************************************************
* Name: polyveck_shiftl
*
* Description: Multiply vector of polynomials of Length K by 2^D without modular
* reduction. Assumes input coefficients to be less than 2^{31-D}.
*
* Arguments: - polyveck *v: pointer to input/output vector
**************************************************/
void polyveck_shiftl(polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_shiftl(&v->vec[i]);
}
/*************************************************
* Name: polyveck_ntt
*
* Description: Forward NTT of all polynomials in vector of length K. Output
* coefficients can be up to 16*Q larger than input coefficients.
*
* Arguments: - polyveck *v: pointer to input/output vector
**************************************************/
void polyveck_ntt(polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_ntt(&v->vec[i]);
}
/*************************************************
* Name: polyveck_invntt_tomont
*
* Description: Inverse NTT and multiplication by 2^{32} of polynomials
* in vector of length K. Input coefficients need to be less
* than 2*Q.
*
* Arguments: - polyveck *v: pointer to input/output vector
**************************************************/
void polyveck_invntt_tomont(polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_invntt_tomont(&v->vec[i]);
}
void polyveck_pointwise_poly_montgomery(polyveck *r, const poly *a, const polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_pointwise_montgomery(&r->vec[i], a, &v->vec[i]);
}
/*************************************************
* Name: polyveck_chknorm
*
* Description: Check infinity norm of polynomials in vector of length K.
* Assumes input polyveck to be reduced by polyveck_reduce().
*
* Arguments: - const polyveck *v: pointer to vector
* - int32_t B: norm bound
*
* Returns 0 if norm of all polynomials are strictly smaller than B <= (Q-1)/8
* and 1 otherwise.
**************************************************/
int polyveck_chknorm(const polyveck *v, int32_t bound) {
unsigned int i;
for(i = 0; i < K; ++i)
if(poly_chknorm(&v->vec[i], bound))
return 1;
return 0;
}
/*************************************************
* Name: polyveck_power2round
*
* Description: For all coefficients a of polynomials in vector of length K,
* compute a0, a1 such that a mod^+ Q = a1*2^D + a0
* with -2^{D-1} < a0 <= 2^{D-1}. Assumes coefficients to be
* standard representatives.
*
* Arguments: - polyveck *v1: pointer to output vector of polynomials with
* coefficients a1
* - polyveck *v0: pointer to output vector of polynomials with
* coefficients a0
* - const polyveck *v: pointer to input vector
**************************************************/
void polyveck_power2round(polyveck *v1, polyveck *v0, const polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_power2round(&v1->vec[i], &v0->vec[i], &v->vec[i]);
}
/*************************************************
* Name: polyveck_decompose
*
* Description: For all coefficients a of polynomials in vector of length K,
* compute high and low bits a0, a1 such a mod^+ Q = a1*ALPHA + a0
* with -ALPHA/2 < a0 <= ALPHA/2 except a1 = (Q-1)/ALPHA where we
* set a1 = 0 and -ALPHA/2 <= a0 = a mod Q - Q < 0.
* Assumes coefficients to be standard representatives.
*
* Arguments: - polyveck *v1: pointer to output vector of polynomials with
* coefficients a1
* - polyveck *v0: pointer to output vector of polynomials with
* coefficients a0
* - const polyveck *v: pointer to input vector
**************************************************/
void polyveck_decompose(polyveck *v1, polyveck *v0, const polyveck *v) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_decompose(&v1->vec[i], &v0->vec[i], &v->vec[i]);
}
/*************************************************
* Name: polyveck_make_hint
*
* Description: Compute hint vector.
*
* Arguments: - polyveck *h: pointer to output vector
* - const polyveck *v0: pointer to low part of input vector
* - const polyveck *v1: pointer to high part of input vector
*
* Returns number of 1 bits.
**************************************************/
unsigned int polyveck_make_hint(polyveck *h,
const polyveck *v0,
const polyveck *v1)
{
unsigned int i, s = 0;
for(i = 0; i < K; ++i)
s += poly_make_hint(&h->vec[i], &v0->vec[i], &v1->vec[i]);
return s;
}
/*************************************************
* Name: polyveck_use_hint
*
* Description: Use hint vector to correct the high bits of input vector.
*
* Arguments: - polyveck *w: pointer to output vector of polynomials with
* corrected high bits
* - const polyveck *u: pointer to input vector
* - const polyveck *h: pointer to input hint vector
**************************************************/
void polyveck_use_hint(polyveck *w, const polyveck *u, const polyveck *h) {
unsigned int i;
for(i = 0; i < K; ++i)
poly_use_hint(&w->vec[i], &u->vec[i], &h->vec[i]);
}
void polyveck_pack_w1(uint8_t r[K*POLYW1_PACKEDBYTES], const polyveck *w1) {
unsigned int i;
for(i = 0; i < K; ++i)
polyw1_pack(&r[i*POLYW1_PACKEDBYTES], &w1->vec[i]);
}

View file

@ -0,0 +1,93 @@
#ifndef POLYVEC_H
#define POLYVEC_H
#include <stdint.h>
#include "params.h"
#include "poly.h"
/* Vectors of polynomials of length L */
typedef struct {
poly vec[L];
} polyvecl;
#define polyvecl_uniform_eta DILITHIUM_NAMESPACE(polyvecl_uniform_eta)
void polyvecl_uniform_eta(polyvecl *v, const uint8_t seed[CRHBYTES], uint16_t nonce);
#define polyvecl_uniform_gamma1 DILITHIUM_NAMESPACE(polyvecl_uniform_gamma1)
void polyvecl_uniform_gamma1(polyvecl *v, const uint8_t seed[CRHBYTES], uint16_t nonce);
#define polyvecl_reduce DILITHIUM_NAMESPACE(polyvecl_reduce)
void polyvecl_reduce(polyvecl *v);
#define polyvecl_add DILITHIUM_NAMESPACE(polyvecl_add)
void polyvecl_add(polyvecl *w, const polyvecl *u, const polyvecl *v);
#define polyvecl_ntt DILITHIUM_NAMESPACE(polyvecl_ntt)
void polyvecl_ntt(polyvecl *v);
#define polyvecl_invntt_tomont DILITHIUM_NAMESPACE(polyvecl_invntt_tomont)
void polyvecl_invntt_tomont(polyvecl *v);
#define polyvecl_pointwise_poly_montgomery DILITHIUM_NAMESPACE(polyvecl_pointwise_poly_montgomery)
void polyvecl_pointwise_poly_montgomery(polyvecl *r, const poly *a, const polyvecl *v);
#define polyvecl_pointwise_acc_montgomery \
DILITHIUM_NAMESPACE(polyvecl_pointwise_acc_montgomery)
void polyvecl_pointwise_acc_montgomery(poly *w,
const polyvecl *u,
const polyvecl *v);
#define polyvecl_chknorm DILITHIUM_NAMESPACE(polyvecl_chknorm)
int polyvecl_chknorm(const polyvecl *v, int32_t B);
/* Vectors of polynomials of length K */
typedef struct {
poly vec[K];
} polyveck;
#define polyveck_uniform_eta DILITHIUM_NAMESPACE(polyveck_uniform_eta)
void polyveck_uniform_eta(polyveck *v, const uint8_t seed[CRHBYTES], uint16_t nonce);
#define polyveck_reduce DILITHIUM_NAMESPACE(polyveck_reduce)
void polyveck_reduce(polyveck *v);
#define polyveck_caddq DILITHIUM_NAMESPACE(polyveck_caddq)
void polyveck_caddq(polyveck *v);
#define polyveck_add DILITHIUM_NAMESPACE(polyveck_add)
void polyveck_add(polyveck *w, const polyveck *u, const polyveck *v);
#define polyveck_sub DILITHIUM_NAMESPACE(polyveck_sub)
void polyveck_sub(polyveck *w, const polyveck *u, const polyveck *v);
#define polyveck_shiftl DILITHIUM_NAMESPACE(polyveck_shiftl)
void polyveck_shiftl(polyveck *v);
#define polyveck_ntt DILITHIUM_NAMESPACE(polyveck_ntt)
void polyveck_ntt(polyveck *v);
#define polyveck_invntt_tomont DILITHIUM_NAMESPACE(polyveck_invntt_tomont)
void polyveck_invntt_tomont(polyveck *v);
#define polyveck_pointwise_poly_montgomery DILITHIUM_NAMESPACE(polyveck_pointwise_poly_montgomery)
void polyveck_pointwise_poly_montgomery(polyveck *r, const poly *a, const polyveck *v);
#define polyveck_chknorm DILITHIUM_NAMESPACE(polyveck_chknorm)
int polyveck_chknorm(const polyveck *v, int32_t B);
#define polyveck_power2round DILITHIUM_NAMESPACE(polyveck_power2round)
void polyveck_power2round(polyveck *v1, polyveck *v0, const polyveck *v);
#define polyveck_decompose DILITHIUM_NAMESPACE(polyveck_decompose)
void polyveck_decompose(polyveck *v1, polyveck *v0, const polyveck *v);
#define polyveck_make_hint DILITHIUM_NAMESPACE(polyveck_make_hint)
unsigned int polyveck_make_hint(polyveck *h,
const polyveck *v0,
const polyveck *v1);
#define polyveck_use_hint DILITHIUM_NAMESPACE(polyveck_use_hint)
void polyveck_use_hint(polyveck *w, const polyveck *v, const polyveck *h);
#define polyveck_pack_w1 DILITHIUM_NAMESPACE(polyveck_pack_w1)
void polyveck_pack_w1(uint8_t r[K*POLYW1_PACKEDBYTES], const polyveck *w1);
#define polyvec_matrix_expand DILITHIUM_NAMESPACE(polyvec_matrix_expand)
void polyvec_matrix_expand(polyvecl mat[K], const uint8_t rho[SEEDBYTES]);
#define polyvec_matrix_pointwise_montgomery DILITHIUM_NAMESPACE(polyvec_matrix_pointwise_montgomery)
void polyvec_matrix_pointwise_montgomery(polyveck *t, const polyvecl mat[K], const polyvecl *v);
#endif

View file

@ -0,0 +1,15 @@
#ifndef VEXTA_PQ_RANDOMBYTES_H
#define VEXTA_PQ_RANDOMBYTES_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* VEXTA: unified prototype. Definition lives in pq_randombytes.cpp and is
backed by Bitcoin's CSPRNG (GetStrongRandBytes). The upstream test-only
randombytes.c files were deleted — they must never ship in a wallet. */
void randombytes(uint8_t *out, size_t outlen);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,69 @@
#include <stdint.h>
#include "params.h"
#include "reduce.h"
/*************************************************
* Name: montgomery_reduce
*
* Description: For finite field element a with -2^{31}Q <= a <= Q*2^31,
* compute r \equiv a*2^{-32} (mod Q) such that -Q < r < Q.
*
* Arguments: - int64_t: finite field element a
*
* Returns r.
**************************************************/
int32_t montgomery_reduce(int64_t a) {
int32_t t;
t = (int64_t)(int32_t)a*QINV;
t = (a - (int64_t)t*Q) >> 32;
return t;
}
/*************************************************
* Name: reduce32
*
* Description: For finite field element a with a <= 2^{31} - 2^{22} - 1,
* compute r \equiv a (mod Q) such that -6283008 <= r <= 6283008.
*
* Arguments: - int32_t: finite field element a
*
* Returns r.
**************************************************/
int32_t reduce32(int32_t a) {
int32_t t;
t = (a + (1 << 22)) >> 23;
t = a - t*Q;
return t;
}
/*************************************************
* Name: caddq
*
* Description: Add Q if input coefficient is negative.
*
* Arguments: - int32_t: finite field element a
*
* Returns r.
**************************************************/
int32_t caddq(int32_t a) {
a += (a >> 31) & Q;
return a;
}
/*************************************************
* Name: freeze
*
* Description: For finite field element a, compute standard
* representative r = a mod^+ Q.
*
* Arguments: - int32_t: finite field element a
*
* Returns r.
**************************************************/
int32_t freeze(int32_t a) {
a = reduce32(a);
a = caddq(a);
return a;
}

View file

@ -0,0 +1,22 @@
#ifndef REDUCE_H
#define REDUCE_H
#include <stdint.h>
#include "params.h"
#define MONT -4186625 // 2^32 % Q
#define QINV 58728449 // q^(-1) mod 2^32
#define montgomery_reduce DILITHIUM_NAMESPACE(montgomery_reduce)
int32_t montgomery_reduce(int64_t a);
#define reduce32 DILITHIUM_NAMESPACE(reduce32)
int32_t reduce32(int32_t a);
#define caddq DILITHIUM_NAMESPACE(caddq)
int32_t caddq(int32_t a);
#define freeze DILITHIUM_NAMESPACE(freeze)
int32_t freeze(int32_t a);
#endif

View file

@ -0,0 +1,102 @@
#include <stdint.h>
#include "params.h"
#include "rounding.h"
/*************************************************
* Name: power2round
*
* Description: For finite field element a, compute a0, a1 such that
* a mod^+ Q = a1*2^D + a0 with -2^{D-1} < a0 <= 2^{D-1}.
* Assumes a to be standard representative.
*
* Arguments: - int32_t a: input element
* - int32_t *a0: pointer to output element a0
*
* Returns a1.
**************************************************/
int32_t power2round(int32_t *a0, int32_t a) {
int32_t a1;
a1 = (a + (1 << (D-1)) - 1) >> D;
*a0 = a - (a1 << D);
return a1;
}
/*************************************************
* Name: decompose
*
* Description: For finite field element a, compute high and low bits a0, a1 such
* that a mod^+ Q = a1*ALPHA + a0 with -ALPHA/2 < a0 <= ALPHA/2 except
* if a1 = (Q-1)/ALPHA where we set a1 = 0 and
* -ALPHA/2 <= a0 = a mod^+ Q - Q < 0. Assumes a to be standard
* representative.
*
* Arguments: - int32_t a: input element
* - int32_t *a0: pointer to output element a0
*
* Returns a1.
**************************************************/
int32_t decompose(int32_t *a0, int32_t a) {
int32_t a1;
a1 = (a + 127) >> 7;
#if GAMMA2 == (Q-1)/32
a1 = (a1*1025 + (1 << 21)) >> 22;
a1 &= 15;
#elif GAMMA2 == (Q-1)/88
a1 = (a1*11275 + (1 << 23)) >> 24;
a1 ^= ((43 - a1) >> 31) & a1;
#endif
*a0 = a - a1*2*GAMMA2;
*a0 -= (((Q-1)/2 - *a0) >> 31) & Q;
return a1;
}
/*************************************************
* Name: make_hint
*
* Description: Compute hint bit indicating whether the low bits of the
* input element overflow into the high bits.
*
* Arguments: - int32_t a0: low bits of input element
* - int32_t a1: high bits of input element
*
* Returns 1 if overflow.
**************************************************/
unsigned int make_hint(int32_t a0, int32_t a1) {
if(a0 > GAMMA2 || a0 < -GAMMA2 || (a0 == -GAMMA2 && a1 != 0))
return 1;
return 0;
}
/*************************************************
* Name: use_hint
*
* Description: Correct high bits according to hint.
*
* Arguments: - int32_t a: input element
* - unsigned int hint: hint bit
*
* Returns corrected high bits.
**************************************************/
int32_t use_hint(int32_t a, unsigned int hint) {
int32_t a0, a1;
a1 = decompose(&a0, a);
if(hint == 0)
return a1;
#if GAMMA2 == (Q-1)/32
if(a0 > 0)
return (a1 + 1) & 15;
else
return (a1 - 1) & 15;
#elif GAMMA2 == (Q-1)/88
if(a0 > 0)
return (a1 == 43) ? 0 : a1 + 1;
else
return (a1 == 0) ? 43 : a1 - 1;
#endif
}

View file

@ -0,0 +1,19 @@
#ifndef ROUNDING_H
#define ROUNDING_H
#include <stdint.h>
#include "params.h"
#define power2round DILITHIUM_NAMESPACE(power2round)
int32_t power2round(int32_t *a0, int32_t a);
#define decompose DILITHIUM_NAMESPACE(decompose)
int32_t decompose(int32_t *a0, int32_t a);
#define make_hint DILITHIUM_NAMESPACE(make_hint)
unsigned int make_hint(int32_t a0, int32_t a1);
#define use_hint DILITHIUM_NAMESPACE(use_hint)
int32_t use_hint(int32_t a, unsigned int hint);
#endif

View file

@ -0,0 +1,477 @@
#include <stdint.h>
#include "params.h"
#include "sign.h"
#include "packing.h"
#include "polyvec.h"
#include "poly.h"
#include "randombytes.h"
#include "symmetric.h"
#include "fips202.h"
#ifndef VEXTA_PQ_VERIFY_ONLY
/*************************************************
* Name: crypto_sign_keypair
*
* Description: Generates public and private key.
*
* Arguments: - uint8_t *pk: pointer to output public key (allocated
* array of CRYPTO_PUBLICKEYBYTES bytes)
* - uint8_t *sk: pointer to output private key (allocated
* array of CRYPTO_SECRETKEYBYTES bytes)
*
* Returns 0 (success)
**************************************************/
int crypto_sign_keypair_from_seed(uint8_t *pk, uint8_t *sk, const uint8_t *seed) {
uint8_t seedbuf[2*SEEDBYTES + CRHBYTES];
uint8_t tr[TRBYTES];
const uint8_t *rho, *rhoprime, *key;
polyvecl mat[K];
polyvecl s1, s1hat;
polyveck s2, t1, t0;
for (int _i=0;_i<SEEDBYTES;_i++) seedbuf[_i]=seed[_i];
seedbuf[SEEDBYTES+0] = K;
seedbuf[SEEDBYTES+1] = L;
shake256(seedbuf, 2*SEEDBYTES + CRHBYTES, seedbuf, SEEDBYTES+2);
rho = seedbuf;
rhoprime = rho + SEEDBYTES;
key = rhoprime + CRHBYTES;
polyvec_matrix_expand(mat, rho);
polyvecl_uniform_eta(&s1, rhoprime, 0);
polyveck_uniform_eta(&s2, rhoprime, L);
s1hat = s1;
polyvecl_ntt(&s1hat);
polyvec_matrix_pointwise_montgomery(&t1, mat, &s1hat);
polyveck_reduce(&t1);
polyveck_invntt_tomont(&t1);
polyveck_add(&t1, &t1, &s2);
polyveck_caddq(&t1);
polyveck_power2round(&t1, &t0, &t1);
pack_pk(pk, rho, &t1);
shake256(tr, TRBYTES, pk, CRYPTO_PUBLICKEYBYTES);
pack_sk(sk, rho, tr, key, &t0, &s1, &s2);
return 0;
}
int crypto_sign_keypair(uint8_t *pk, uint8_t *sk) {
uint8_t seedbuf[2*SEEDBYTES + CRHBYTES];
uint8_t tr[TRBYTES];
const uint8_t *rho, *rhoprime, *key;
polyvecl mat[K];
polyvecl s1, s1hat;
polyveck s2, t1, t0;
/* Get randomness for rho, rhoprime and key */
randombytes(seedbuf, SEEDBYTES);
seedbuf[SEEDBYTES+0] = K;
seedbuf[SEEDBYTES+1] = L;
shake256(seedbuf, 2*SEEDBYTES + CRHBYTES, seedbuf, SEEDBYTES+2);
rho = seedbuf;
rhoprime = rho + SEEDBYTES;
key = rhoprime + CRHBYTES;
/* Expand matrix */
polyvec_matrix_expand(mat, rho);
/* Sample short vectors s1 and s2 */
polyvecl_uniform_eta(&s1, rhoprime, 0);
polyveck_uniform_eta(&s2, rhoprime, L);
/* Matrix-vector multiplication */
s1hat = s1;
polyvecl_ntt(&s1hat);
polyvec_matrix_pointwise_montgomery(&t1, mat, &s1hat);
polyveck_reduce(&t1);
polyveck_invntt_tomont(&t1);
/* Add error vector s2 */
polyveck_add(&t1, &t1, &s2);
/* Extract t1 and write public key */
polyveck_caddq(&t1);
polyveck_power2round(&t1, &t0, &t1);
pack_pk(pk, rho, &t1);
/* Compute H(rho, t1) and write secret key */
shake256(tr, TRBYTES, pk, CRYPTO_PUBLICKEYBYTES);
pack_sk(sk, rho, tr, key, &t0, &s1, &s2);
return 0;
}
/*************************************************
* Name: crypto_sign_signature_internal
*
* Description: Computes signature. Internal API.
*
* Arguments: - uint8_t *sig: pointer to output signature (of length CRYPTO_BYTES)
* - size_t *siglen: pointer to output length of signature
* - uint8_t *m: pointer to message to be signed
* - size_t mlen: length of message
* - uint8_t *pre: pointer to prefix string
* - size_t prelen: length of prefix string
* - uint8_t *rnd: pointer to random seed
* - uint8_t *sk: pointer to bit-packed secret key
*
* Returns 0 (success)
**************************************************/
int crypto_sign_signature_internal(uint8_t *sig,
size_t *siglen,
const uint8_t *m,
size_t mlen,
const uint8_t *pre,
size_t prelen,
const uint8_t rnd[RNDBYTES],
const uint8_t *sk)
{
unsigned int n;
uint8_t seedbuf[2*SEEDBYTES + TRBYTES + 2*CRHBYTES];
uint8_t *rho, *tr, *key, *mu, *rhoprime;
uint16_t nonce = 0;
polyvecl mat[K], s1, y, z;
polyveck t0, s2, w1, w0, h;
poly cp;
keccak_state state;
rho = seedbuf;
tr = rho + SEEDBYTES;
key = tr + TRBYTES;
mu = key + SEEDBYTES;
rhoprime = mu + CRHBYTES;
unpack_sk(rho, tr, key, &t0, &s1, &s2, sk);
/* Compute mu = CRH(tr, pre, msg) */
shake256_init(&state);
shake256_absorb(&state, tr, TRBYTES);
shake256_absorb(&state, pre, prelen);
shake256_absorb(&state, m, mlen);
shake256_finalize(&state);
shake256_squeeze(mu, CRHBYTES, &state);
/* Compute rhoprime = CRH(key, rnd, mu) */
shake256_init(&state);
shake256_absorb(&state, key, SEEDBYTES);
shake256_absorb(&state, rnd, RNDBYTES);
shake256_absorb(&state, mu, CRHBYTES);
shake256_finalize(&state);
shake256_squeeze(rhoprime, CRHBYTES, &state);
/* Expand matrix and transform vectors */
polyvec_matrix_expand(mat, rho);
polyvecl_ntt(&s1);
polyveck_ntt(&s2);
polyveck_ntt(&t0);
rej:
/* Sample intermediate vector y */
polyvecl_uniform_gamma1(&y, rhoprime, nonce++);
/* Matrix-vector multiplication */
z = y;
polyvecl_ntt(&z);
polyvec_matrix_pointwise_montgomery(&w1, mat, &z);
polyveck_reduce(&w1);
polyveck_invntt_tomont(&w1);
/* Decompose w and call the random oracle */
polyveck_caddq(&w1);
polyveck_decompose(&w1, &w0, &w1);
polyveck_pack_w1(sig, &w1);
shake256_init(&state);
shake256_absorb(&state, mu, CRHBYTES);
shake256_absorb(&state, sig, K*POLYW1_PACKEDBYTES);
shake256_finalize(&state);
shake256_squeeze(sig, CTILDEBYTES, &state);
poly_challenge(&cp, sig);
poly_ntt(&cp);
/* Compute z, reject if it reveals secret */
polyvecl_pointwise_poly_montgomery(&z, &cp, &s1);
polyvecl_invntt_tomont(&z);
polyvecl_add(&z, &z, &y);
polyvecl_reduce(&z);
if(polyvecl_chknorm(&z, GAMMA1 - BETA))
goto rej;
/* Check that subtracting cs2 does not change high bits of w and low bits
* do not reveal secret information */
polyveck_pointwise_poly_montgomery(&h, &cp, &s2);
polyveck_invntt_tomont(&h);
polyveck_sub(&w0, &w0, &h);
polyveck_reduce(&w0);
if(polyveck_chknorm(&w0, GAMMA2 - BETA))
goto rej;
/* Compute hints for w1 */
polyveck_pointwise_poly_montgomery(&h, &cp, &t0);
polyveck_invntt_tomont(&h);
polyveck_reduce(&h);
if(polyveck_chknorm(&h, GAMMA2))
goto rej;
polyveck_add(&w0, &w0, &h);
n = polyveck_make_hint(&h, &w0, &w1);
if(n > OMEGA)
goto rej;
/* Write signature */
pack_sig(sig, sig, &z, &h);
*siglen = CRYPTO_BYTES;
return 0;
}
/*************************************************
* Name: crypto_sign_signature
*
* Description: Computes signature.
*
* Arguments: - uint8_t *sig: pointer to output signature (of length CRYPTO_BYTES)
* - size_t *siglen: pointer to output length of signature
* - uint8_t *m: pointer to message to be signed
* - size_t mlen: length of message
* - uint8_t *ctx: pointer to contex string
* - size_t ctxlen: length of contex string
* - uint8_t *sk: pointer to bit-packed secret key
*
* Returns 0 (success) or -1 (context string too long)
**************************************************/
int crypto_sign_signature(uint8_t *sig,
size_t *siglen,
const uint8_t *m,
size_t mlen,
const uint8_t *ctx,
size_t ctxlen,
const uint8_t *sk)
{
size_t i;
uint8_t pre[257];
uint8_t rnd[RNDBYTES];
if(ctxlen > 255)
return -1;
/* Prepare pre = (0, ctxlen, ctx) */
pre[0] = 0;
pre[1] = ctxlen;
for(i = 0; i < ctxlen; i++)
pre[2 + i] = ctx[i];
#ifdef DILITHIUM_RANDOMIZED_SIGNING
randombytes(rnd, RNDBYTES);
#else
for(i=0;i<RNDBYTES;i++)
rnd[i] = 0;
#endif
crypto_sign_signature_internal(sig,siglen,m,mlen,pre,2+ctxlen,rnd,sk);
return 0;
}
/*************************************************
* Name: crypto_sign
*
* Description: Compute signed message.
*
* Arguments: - uint8_t *sm: pointer to output signed message (allocated
* array with CRYPTO_BYTES + mlen bytes),
* can be equal to m
* - size_t *smlen: pointer to output length of signed
* message
* - const uint8_t *m: pointer to message to be signed
* - size_t mlen: length of message
* - const uint8_t *ctx: pointer to context string
* - size_t ctxlen: length of context string
* - const uint8_t *sk: pointer to bit-packed secret key
*
* Returns 0 (success) or -1 (context string too long)
**************************************************/
int crypto_sign(uint8_t *sm,
size_t *smlen,
const uint8_t *m,
size_t mlen,
const uint8_t *ctx,
size_t ctxlen,
const uint8_t *sk)
{
int ret;
size_t i;
for(i = 0; i < mlen; ++i)
sm[CRYPTO_BYTES + mlen - 1 - i] = m[mlen - 1 - i];
ret = crypto_sign_signature(sm, smlen, sm + CRYPTO_BYTES, mlen, ctx, ctxlen, sk);
*smlen += mlen;
return ret;
}
#endif // !VEXTA_PQ_VERIFY_ONLY
/*************************************************
* Name: crypto_sign_verify_internal
*
* Description: Verifies signature. Internal API.
*
* Arguments: - uint8_t *m: pointer to input signature
* - size_t siglen: length of signature
* - const uint8_t *m: pointer to message
* - size_t mlen: length of message
* - const uint8_t *pre: pointer to prefix string
* - size_t prelen: length of prefix string
* - const uint8_t *pk: pointer to bit-packed public key
*
* Returns 0 if signature could be verified correctly and -1 otherwise
**************************************************/
int crypto_sign_verify_internal(const uint8_t *sig,
size_t siglen,
const uint8_t *m,
size_t mlen,
const uint8_t *pre,
size_t prelen,
const uint8_t *pk)
{
unsigned int i;
uint8_t buf[K*POLYW1_PACKEDBYTES];
uint8_t rho[SEEDBYTES];
uint8_t mu[CRHBYTES];
uint8_t c[CTILDEBYTES];
uint8_t c2[CTILDEBYTES];
poly cp;
polyvecl mat[K], z;
polyveck t1, w1, h;
keccak_state state;
if(siglen != CRYPTO_BYTES)
return -1;
unpack_pk(rho, &t1, pk);
if(unpack_sig(c, &z, &h, sig))
return -1;
if(polyvecl_chknorm(&z, GAMMA1 - BETA))
return -1;
/* Compute CRH(H(rho, t1), pre, msg) */
shake256(mu, TRBYTES, pk, CRYPTO_PUBLICKEYBYTES);
shake256_init(&state);
shake256_absorb(&state, mu, TRBYTES);
shake256_absorb(&state, pre, prelen);
shake256_absorb(&state, m, mlen);
shake256_finalize(&state);
shake256_squeeze(mu, CRHBYTES, &state);
/* Matrix-vector multiplication; compute Az - c2^dt1 */
poly_challenge(&cp, c);
polyvec_matrix_expand(mat, rho);
polyvecl_ntt(&z);
polyvec_matrix_pointwise_montgomery(&w1, mat, &z);
poly_ntt(&cp);
polyveck_shiftl(&t1);
polyveck_ntt(&t1);
polyveck_pointwise_poly_montgomery(&t1, &cp, &t1);
polyveck_sub(&w1, &w1, &t1);
polyveck_reduce(&w1);
polyveck_invntt_tomont(&w1);
/* Reconstruct w1 */
polyveck_caddq(&w1);
polyveck_use_hint(&w1, &w1, &h);
polyveck_pack_w1(buf, &w1);
/* Call random oracle and verify challenge */
shake256_init(&state);
shake256_absorb(&state, mu, CRHBYTES);
shake256_absorb(&state, buf, K*POLYW1_PACKEDBYTES);
shake256_finalize(&state);
shake256_squeeze(c2, CTILDEBYTES, &state);
for(i = 0; i < CTILDEBYTES; ++i)
if(c[i] != c2[i])
return -1;
return 0;
}
/*************************************************
* Name: crypto_sign_verify
*
* Description: Verifies signature.
*
* Arguments: - uint8_t *m: pointer to input signature
* - size_t siglen: length of signature
* - const uint8_t *m: pointer to message
* - size_t mlen: length of message
* - const uint8_t *ctx: pointer to context string
* - size_t ctxlen: length of context string
* - const uint8_t *pk: pointer to bit-packed public key
*
* Returns 0 if signature could be verified correctly and -1 otherwise
**************************************************/
int crypto_sign_verify(const uint8_t *sig,
size_t siglen,
const uint8_t *m,
size_t mlen,
const uint8_t *ctx,
size_t ctxlen,
const uint8_t *pk)
{
size_t i;
uint8_t pre[257];
if(ctxlen > 255)
return -1;
pre[0] = 0;
pre[1] = ctxlen;
for(i = 0; i < ctxlen; i++)
pre[2 + i] = ctx[i];
return crypto_sign_verify_internal(sig,siglen,m,mlen,pre,2+ctxlen,pk);
}
/*************************************************
* Name: crypto_sign_open
*
* Description: Verify signed message.
*
* Arguments: - uint8_t *m: pointer to output message (allocated
* array with smlen bytes), can be equal to sm
* - size_t *mlen: pointer to output length of message
* - const uint8_t *sm: pointer to signed message
* - size_t smlen: length of signed message
* - const uint8_t *ctx: pointer to context tring
* - size_t ctxlen: length of context string
* - const uint8_t *pk: pointer to bit-packed public key
*
* Returns 0 if signed message could be verified correctly and -1 otherwise
**************************************************/
int crypto_sign_open(uint8_t *m,
size_t *mlen,
const uint8_t *sm,
size_t smlen,
const uint8_t *ctx,
size_t ctxlen,
const uint8_t *pk)
{
size_t i;
if(smlen < CRYPTO_BYTES)
goto badsig;
*mlen = smlen - CRYPTO_BYTES;
if(crypto_sign_verify(sm, CRYPTO_BYTES, sm + CRYPTO_BYTES, *mlen, ctx, ctxlen, pk))
goto badsig;
else {
/* All good, copy msg, return 0 */
for(i = 0; i < *mlen; ++i)
m[i] = sm[CRYPTO_BYTES + i];
return 0;
}
badsig:
/* Signature verification failed */
*mlen = 0;
for(i = 0; i < smlen; ++i)
m[i] = 0;
return -1;
}

View file

@ -0,0 +1,56 @@
#ifndef SIGN_H
#define SIGN_H
#include <stddef.h>
#include <stdint.h>
#include "params.h"
#include "polyvec.h"
#include "poly.h"
#define crypto_sign_keypair DILITHIUM_NAMESPACE(keypair)
int crypto_sign_keypair(uint8_t *pk, uint8_t *sk);
#define crypto_sign_signature_internal DILITHIUM_NAMESPACE(signature_internal)
int crypto_sign_signature_internal(uint8_t *sig,
size_t *siglen,
const uint8_t *m,
size_t mlen,
const uint8_t *pre,
size_t prelen,
const uint8_t rnd[RNDBYTES],
const uint8_t *sk);
#define crypto_sign_signature DILITHIUM_NAMESPACE(signature)
int crypto_sign_signature(uint8_t *sig, size_t *siglen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *sk);
#define crypto_sign DILITHIUM_NAMESPACETOP
int crypto_sign(uint8_t *sm, size_t *smlen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *sk);
#define crypto_sign_verify_internal DILITHIUM_NAMESPACE(verify_internal)
int crypto_sign_verify_internal(const uint8_t *sig,
size_t siglen,
const uint8_t *m,
size_t mlen,
const uint8_t *pre,
size_t prelen,
const uint8_t *pk);
#define crypto_sign_verify DILITHIUM_NAMESPACE(verify)
int crypto_sign_verify(const uint8_t *sig, size_t siglen,
const uint8_t *m, size_t mlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *pk);
#define crypto_sign_open DILITHIUM_NAMESPACE(open)
int crypto_sign_open(uint8_t *m, size_t *mlen,
const uint8_t *sm, size_t smlen,
const uint8_t *ctx, size_t ctxlen,
const uint8_t *pk);
#endif

View file

@ -0,0 +1,28 @@
#include <stdint.h>
#include "params.h"
#include "symmetric.h"
#include "fips202.h"
void dilithium_shake128_stream_init(keccak_state *state, const uint8_t seed[SEEDBYTES], uint16_t nonce)
{
uint8_t t[2];
t[0] = nonce;
t[1] = nonce >> 8;
shake128_init(state);
shake128_absorb(state, seed, SEEDBYTES);
shake128_absorb(state, t, 2);
shake128_finalize(state);
}
void dilithium_shake256_stream_init(keccak_state *state, const uint8_t seed[CRHBYTES], uint16_t nonce)
{
uint8_t t[2];
t[0] = nonce;
t[1] = nonce >> 8;
shake256_init(state);
shake256_absorb(state, seed, CRHBYTES);
shake256_absorb(state, t, 2);
shake256_finalize(state);
}

View file

@ -0,0 +1,34 @@
#ifndef SYMMETRIC_H
#define SYMMETRIC_H
#include <stdint.h>
#include "params.h"
#include "fips202.h"
typedef keccak_state stream128_state;
typedef keccak_state stream256_state;
#define dilithium_shake128_stream_init DILITHIUM_NAMESPACE(dilithium_shake128_stream_init)
void dilithium_shake128_stream_init(keccak_state *state,
const uint8_t seed[SEEDBYTES],
uint16_t nonce);
#define dilithium_shake256_stream_init DILITHIUM_NAMESPACE(dilithium_shake256_stream_init)
void dilithium_shake256_stream_init(keccak_state *state,
const uint8_t seed[CRHBYTES],
uint16_t nonce);
#define STREAM128_BLOCKBYTES SHAKE128_RATE
#define STREAM256_BLOCKBYTES SHAKE256_RATE
#define stream128_init(STATE, SEED, NONCE) \
dilithium_shake128_stream_init(STATE, SEED, NONCE)
#define stream128_squeezeblocks(OUT, OUTBLOCKS, STATE) \
shake128_squeezeblocks(OUT, OUTBLOCKS, STATE)
#define stream256_init(STATE, SEED, NONCE) \
dilithium_shake256_stream_init(STATE, SEED, NONCE)
#define stream256_squeezeblocks(OUT, OUTBLOCKS, STATE) \
shake256_squeezeblocks(OUT, OUTBLOCKS, STATE)
#endif

View file

@ -0,0 +1,37 @@
// VEXTA: wrapper impl. Bridges to the namespaced C reference symbols.
#include "pq.h"
extern "C" {
// ML-DSA-65 (DILITHIUM_MODE=3 -> pqcrystals_dilithium3_ref_*)
int pqcrystals_dilithium3_ref_keypair(uint8_t* pk, uint8_t* sk);
int crypto_sign_keypair_from_seed(uint8_t* pk, uint8_t* sk, const uint8_t* seed);
int pqcrystals_dilithium3_ref_signature(uint8_t* sig, size_t* siglen,
const uint8_t* m, size_t mlen, const uint8_t* ctx, size_t ctxlen, const uint8_t* sk);
int pqcrystals_dilithium3_ref_verify(const uint8_t* sig, size_t siglen,
const uint8_t* m, size_t mlen, const uint8_t* ctx, size_t ctxlen, const uint8_t* pk);
// SPHINCS+ (bare crypto_sign_* in ref)
int crypto_sign_keypair(uint8_t* pk, uint8_t* sk);
int crypto_sign_seed_keypair(uint8_t* pk, uint8_t* sk, const uint8_t* seed);
int crypto_sign_signature(uint8_t* sig, size_t* siglen, const uint8_t* m, size_t mlen, const uint8_t* sk);
int crypto_sign_verify(const uint8_t* sig, size_t siglen, const uint8_t* m, size_t mlen, const uint8_t* pk);
}
namespace pq {
#ifndef VEXTA_PQ_VERIFY_ONLY
bool MLDSA65::keygen(uint8_t* pk, uint8_t* sk){ return pqcrystals_dilithium3_ref_keypair(pk,sk)==0; }
bool MLDSA65::keygen_from_seed(uint8_t* pk, uint8_t* sk, const uint8_t* seed){ return crypto_sign_keypair_from_seed(pk,sk,seed)==0; }
bool MLDSA65::sign(uint8_t* sig,size_t* sl,const uint8_t* m,size_t ml,const uint8_t* sk){
return pqcrystals_dilithium3_ref_signature(sig,sl,m,ml,nullptr,0,sk)==0; }
#endif
bool MLDSA65::verify(const uint8_t* sig,size_t sl,const uint8_t* m,size_t ml,const uint8_t* pk){
return pqcrystals_dilithium3_ref_verify(sig,sl,m,ml,nullptr,0,pk)==0; }
#ifndef VEXTA_PQ_VERIFY_ONLY
bool SPHINCS128s::keygen(uint8_t* pk,uint8_t* sk){ return crypto_sign_keypair(pk,sk)==0; }
bool SPHINCS128s::keygen_from_seed(uint8_t* pk,uint8_t* sk,const uint8_t* seed){ return crypto_sign_seed_keypair(pk,sk,seed)==0; }
bool SPHINCS128s::sign(uint8_t* sig,size_t* sl,const uint8_t* m,size_t ml,const uint8_t* sk){
return crypto_sign_signature(sig,sl,m,ml,sk)==0; }
#endif
bool SPHINCS128s::verify(const uint8_t* sig,size_t sl,const uint8_t* m,size_t ml,const uint8_t* pk){
return crypto_sign_verify(sig,sl,m,ml,pk)==0; }
}

30
app/src/main/cpp/pq/pq.h Normal file
View file

@ -0,0 +1,30 @@
// VEXTA: uniform post-quantum signature interface over the vendored C references.
#ifndef VEXTA_CRYPTO_PQ_H
#define VEXTA_CRYPTO_PQ_H
#include <cstdint>
#include <cstddef>
#include <vector>
namespace pq {
struct MLDSA65 {
static constexpr size_t PUBKEY_BYTES = 1952; // ML-DSA-65 (FIPS 204)
static constexpr size_t SECKEY_BYTES = 4032;
static constexpr size_t SIG_BYTES = 3309;
static bool keygen(uint8_t* pk, uint8_t* sk);
static bool keygen_from_seed(uint8_t* pk, uint8_t* sk, const uint8_t* seed);
static bool sign(uint8_t* sig, size_t* siglen, const uint8_t* msg, size_t msglen, const uint8_t* sk);
static bool verify(const uint8_t* sig, size_t siglen, const uint8_t* msg, size_t msglen, const uint8_t* pk);
};
struct SPHINCS128s {
static constexpr size_t PUBKEY_BYTES = 32; // SLH-DSA sha2-128s (FIPS 205)
static constexpr size_t SECKEY_BYTES = 64;
static constexpr size_t SIG_BYTES = 7856;
static bool keygen(uint8_t* pk, uint8_t* sk);
static bool keygen_from_seed(uint8_t* pk, uint8_t* sk, const uint8_t* seed);
static bool sign(uint8_t* sig, size_t* siglen, const uint8_t* msg, size_t msglen, const uint8_t* sk);
static bool verify(const uint8_t* sig, size_t siglen, const uint8_t* msg, size_t msglen, const uint8_t* pk);
};
} // namespace pq
#endif

View file

@ -0,0 +1,2 @@
SPDX-License-Identifier: (LicenseRef-SPHINCS-PLUS-Public-Domain OR CC0-1.0 OR 0BSD OR MIT-0) AND MIT

View file

@ -0,0 +1,104 @@
#include <stdint.h>
#include <string.h>
#include "address.h"
#include "params.h"
#include "utils.h"
/*
* Specify which level of Merkle tree (the "layer") we're working on
*/
void set_layer_addr(uint32_t addr[8], uint32_t layer)
{
((unsigned char *)addr)[SPX_OFFSET_LAYER] = (unsigned char)layer;
}
/*
* Specify which Merkle tree within the level (the "tree address") we're working on
*/
void set_tree_addr(uint32_t addr[8], uint64_t tree)
{
#if (SPX_TREE_HEIGHT * (SPX_D - 1)) > 64
#error Subtree addressing is currently limited to at most 2^64 trees
#endif
ull_to_bytes(&((unsigned char *)addr)[SPX_OFFSET_TREE], 8, tree );
}
/*
* Specify the reason we'll use this address structure for, that is, what
* hash will we compute with it. This is used so that unrelated types of
* hashes don't accidentally get the same address structure. The type will be
* one of the SPX_ADDR_TYPE constants
*/
void set_type(uint32_t addr[8], uint32_t type)
{
((unsigned char *)addr)[SPX_OFFSET_TYPE] = (unsigned char)type;
}
/*
* Copy the layer and tree fields of the address structure. This is used
* when we're doing multiple types of hashes within the same Merkle tree
*/
void copy_subtree_addr(uint32_t out[8], const uint32_t in[8])
{
memcpy( out, in, SPX_OFFSET_TREE+8 );
}
/* These functions are used for OTS addresses. */
/*
* Specify which Merkle leaf we're working on; that is, which OTS keypair
* we're talking about.
*/
void set_keypair_addr(uint32_t addr[8], uint32_t keypair)
{
u32_to_bytes(&((unsigned char *)addr)[SPX_OFFSET_KP_ADDR], keypair);
}
/*
* Copy the layer, tree and keypair fields of the address structure. This is
* used when we're doing multiple things within the same OTS keypair
*/
void copy_keypair_addr(uint32_t out[8], const uint32_t in[8])
{
memcpy( out, in, SPX_OFFSET_TREE+8 );
memcpy( (unsigned char *)out + SPX_OFFSET_KP_ADDR, (unsigned char *)in + SPX_OFFSET_KP_ADDR, 4);
}
/*
* Specify which Merkle chain within the OTS we're working with
* (the chain address)
*/
void set_chain_addr(uint32_t addr[8], uint32_t chain)
{
((unsigned char *)addr)[SPX_OFFSET_CHAIN_ADDR] = (unsigned char)chain;
}
/*
* Specify where in the Merkle chain we are
* (the hash address)
*/
void set_hash_addr(uint32_t addr[8], uint32_t hash)
{
((unsigned char *)addr)[SPX_OFFSET_HASH_ADDR] = (unsigned char)hash;
}
/* These functions are used for all hash tree addresses (including FORS). */
/*
* Specify the height of the node in the Merkle/FORS tree we are in
* (the tree height)
*/
void set_tree_height(uint32_t addr[8], uint32_t tree_height)
{
((unsigned char *)addr)[SPX_OFFSET_TREE_HGT] = (unsigned char)tree_height;
}
/*
* Specify the distance from the left edge of the node in the Merkle/FORS tree
* (the tree index)
*/
void set_tree_index(uint32_t addr[8], uint32_t tree_index)
{
u32_to_bytes(&((unsigned char *)addr)[SPX_OFFSET_TREE_INDEX], tree_index );
}

View file

@ -0,0 +1,51 @@
#ifndef SPX_ADDRESS_H
#define SPX_ADDRESS_H
#include <stdint.h>
#include "params.h"
/* The hash types that are passed to set_type */
#define SPX_ADDR_TYPE_WOTS 0
#define SPX_ADDR_TYPE_WOTSPK 1
#define SPX_ADDR_TYPE_HASHTREE 2
#define SPX_ADDR_TYPE_FORSTREE 3
#define SPX_ADDR_TYPE_FORSPK 4
#define SPX_ADDR_TYPE_WOTSPRF 5
#define SPX_ADDR_TYPE_FORSPRF 6
#define set_layer_addr SPX_NAMESPACE(set_layer_addr)
void set_layer_addr(uint32_t addr[8], uint32_t layer);
#define set_tree_addr SPX_NAMESPACE(set_tree_addr)
void set_tree_addr(uint32_t addr[8], uint64_t tree);
#define set_type SPX_NAMESPACE(set_type)
void set_type(uint32_t addr[8], uint32_t type);
/* Copies the layer and tree part of one address into the other */
#define copy_subtree_addr SPX_NAMESPACE(copy_subtree_addr)
void copy_subtree_addr(uint32_t out[8], const uint32_t in[8]);
/* These functions are used for WOTS and FORS addresses. */
#define set_keypair_addr SPX_NAMESPACE(set_keypair_addr)
void set_keypair_addr(uint32_t addr[8], uint32_t keypair);
#define set_chain_addr SPX_NAMESPACE(set_chain_addr)
void set_chain_addr(uint32_t addr[8], uint32_t chain);
#define set_hash_addr SPX_NAMESPACE(set_hash_addr)
void set_hash_addr(uint32_t addr[8], uint32_t hash);
#define copy_keypair_addr SPX_NAMESPACE(copy_keypair_addr)
void copy_keypair_addr(uint32_t out[8], const uint32_t in[8]);
/* These functions are used for all hash tree addresses (including FORS). */
#define set_tree_height SPX_NAMESPACE(set_tree_height)
void set_tree_height(uint32_t addr[8], uint32_t tree_height);
#define set_tree_index SPX_NAMESPACE(set_tree_index)
void set_tree_index(uint32_t addr[8], uint32_t tree_index);
#endif

View file

@ -0,0 +1,77 @@
#ifndef SPX_API_H
#define SPX_API_H
#include <stddef.h>
#include <stdint.h>
#include "params.h"
#define CRYPTO_ALGNAME "SPHINCS+"
#define CRYPTO_SECRETKEYBYTES SPX_SK_BYTES
#define CRYPTO_PUBLICKEYBYTES SPX_PK_BYTES
#define CRYPTO_BYTES SPX_BYTES
#define CRYPTO_SEEDBYTES 3*SPX_N
/*
* Returns the length of a secret key, in bytes
*/
unsigned long long crypto_sign_secretkeybytes(void);
/*
* Returns the length of a public key, in bytes
*/
unsigned long long crypto_sign_publickeybytes(void);
/*
* Returns the length of a signature, in bytes
*/
unsigned long long crypto_sign_bytes(void);
/*
* Returns the length of the seed required to generate a key pair, in bytes
*/
unsigned long long crypto_sign_seedbytes(void);
/*
* Generates a SPHINCS+ key pair given a seed.
* Format sk: [SK_SEED || SK_PRF || PUB_SEED || root]
* Format pk: [root || PUB_SEED]
*/
int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk,
const unsigned char *seed);
/*
* Generates a SPHINCS+ key pair.
* Format sk: [SK_SEED || SK_PRF || PUB_SEED || root]
* Format pk: [root || PUB_SEED]
*/
int crypto_sign_keypair(unsigned char *pk, unsigned char *sk);
/**
* Returns an array containing a detached signature.
*/
int crypto_sign_signature(uint8_t *sig, size_t *siglen,
const uint8_t *m, size_t mlen, const uint8_t *sk);
/**
* Verifies a detached signature and message under a given public key.
*/
int crypto_sign_verify(const uint8_t *sig, size_t siglen,
const uint8_t *m, size_t mlen, const uint8_t *pk);
/**
* Returns an array containing the signature followed by the message.
*/
int crypto_sign(unsigned char *sm, unsigned long long *smlen,
const unsigned char *m, unsigned long long mlen,
const unsigned char *sk);
/**
* Verifies a given signature-message pair under a given public key.
*/
int crypto_sign_open(unsigned char *m, unsigned long long *mlen,
const unsigned char *sm, unsigned long long smlen,
const unsigned char *pk);
#endif

View file

@ -0,0 +1,28 @@
#ifndef SPX_CONTEXT_H
#define SPX_CONTEXT_H
#include <stdint.h>
#include "params.h"
typedef struct {
uint8_t pub_seed[SPX_N];
uint8_t sk_seed[SPX_N];
#ifdef SPX_SHA2
// sha256 state that absorbed pub_seed
uint8_t state_seeded[40];
# if SPX_SHA512
// sha512 state that absorbed pub_seed
uint8_t state_seeded_512[72];
# endif
#endif
#ifdef SPX_HARAKA
uint64_t tweaked512_rc64[10][8];
uint32_t tweaked256_rc32[10][8];
#endif
} spx_ctx;
#endif

View file

@ -0,0 +1,598 @@
/* Based on the public domain implementation in
* crypto_hash/keccakc512/simple/ from http://bench.cr.yp.to/supercop.html
* by Ronny Van Keer
* and the public domain "TweetFips202" implementation
* from https://twitter.com/tweetfips202
* by Gilles Van Assche, Daniel J. Bernstein, and Peter Schwabe */
#include <stddef.h>
#include <stdint.h>
#include "fips202.h"
#define NROUNDS 24
#define ROL(a, offset) (((a) << (offset)) ^ ((a) >> (64 - (offset))))
/*************************************************
* Name: load64
*
* Description: Load 8 bytes into uint64_t in little-endian order
*
* Arguments: - const uint8_t *x: pointer to input byte array
*
* Returns the loaded 64-bit unsigned integer
**************************************************/
static uint64_t load64(const uint8_t *x) {
uint64_t r = 0;
for (size_t i = 0; i < 8; ++i) {
r |= (uint64_t)x[i] << 8 * i;
}
return r;
}
/*************************************************
* Name: store64
*
* Description: Store a 64-bit integer to a byte array in little-endian order
*
* Arguments: - uint8_t *x: pointer to the output byte array
* - uint64_t u: input 64-bit unsigned integer
**************************************************/
static void store64(uint8_t *x, uint64_t u) {
for (size_t i = 0; i < 8; ++i) {
x[i] = (uint8_t) (u >> 8 * i);
}
}
/* Keccak round constants */
static const uint64_t KeccakF_RoundConstants[NROUNDS] = {
0x0000000000000001ULL, 0x0000000000008082ULL,
0x800000000000808aULL, 0x8000000080008000ULL,
0x000000000000808bULL, 0x0000000080000001ULL,
0x8000000080008081ULL, 0x8000000000008009ULL,
0x000000000000008aULL, 0x0000000000000088ULL,
0x0000000080008009ULL, 0x000000008000000aULL,
0x000000008000808bULL, 0x800000000000008bULL,
0x8000000000008089ULL, 0x8000000000008003ULL,
0x8000000000008002ULL, 0x8000000000000080ULL,
0x000000000000800aULL, 0x800000008000000aULL,
0x8000000080008081ULL, 0x8000000000008080ULL,
0x0000000080000001ULL, 0x8000000080008008ULL
};
/*************************************************
* Name: KeccakF1600_StatePermute
*
* Description: The Keccak F1600 Permutation
*
* Arguments: - uint64_t *state: pointer to input/output Keccak state
**************************************************/
static void KeccakF1600_StatePermute(uint64_t *state) {
int round;
uint64_t Aba, Abe, Abi, Abo, Abu;
uint64_t Aga, Age, Agi, Ago, Agu;
uint64_t Aka, Ake, Aki, Ako, Aku;
uint64_t Ama, Ame, Ami, Amo, Amu;
uint64_t Asa, Ase, Asi, Aso, Asu;
uint64_t BCa, BCe, BCi, BCo, BCu;
uint64_t Da, De, Di, Do, Du;
uint64_t Eba, Ebe, Ebi, Ebo, Ebu;
uint64_t Ega, Ege, Egi, Ego, Egu;
uint64_t Eka, Eke, Eki, Eko, Eku;
uint64_t Ema, Eme, Emi, Emo, Emu;
uint64_t Esa, Ese, Esi, Eso, Esu;
// copyFromState(A, state)
Aba = state[0];
Abe = state[1];
Abi = state[2];
Abo = state[3];
Abu = state[4];
Aga = state[5];
Age = state[6];
Agi = state[7];
Ago = state[8];
Agu = state[9];
Aka = state[10];
Ake = state[11];
Aki = state[12];
Ako = state[13];
Aku = state[14];
Ama = state[15];
Ame = state[16];
Ami = state[17];
Amo = state[18];
Amu = state[19];
Asa = state[20];
Ase = state[21];
Asi = state[22];
Aso = state[23];
Asu = state[24];
for (round = 0; round < NROUNDS; round += 2) {
// prepareTheta
BCa = Aba ^ Aga ^ Aka ^ Ama ^ Asa;
BCe = Abe ^ Age ^ Ake ^ Ame ^ Ase;
BCi = Abi ^ Agi ^ Aki ^ Ami ^ Asi;
BCo = Abo ^ Ago ^ Ako ^ Amo ^ Aso;
BCu = Abu ^ Agu ^ Aku ^ Amu ^ Asu;
// thetaRhoPiChiIotaPrepareTheta(round , A, E)
Da = BCu ^ ROL(BCe, 1);
De = BCa ^ ROL(BCi, 1);
Di = BCe ^ ROL(BCo, 1);
Do = BCi ^ ROL(BCu, 1);
Du = BCo ^ ROL(BCa, 1);
Aba ^= Da;
BCa = Aba;
Age ^= De;
BCe = ROL(Age, 44);
Aki ^= Di;
BCi = ROL(Aki, 43);
Amo ^= Do;
BCo = ROL(Amo, 21);
Asu ^= Du;
BCu = ROL(Asu, 14);
Eba = BCa ^ ((~BCe) & BCi);
Eba ^= KeccakF_RoundConstants[round];
Ebe = BCe ^ ((~BCi) & BCo);
Ebi = BCi ^ ((~BCo) & BCu);
Ebo = BCo ^ ((~BCu) & BCa);
Ebu = BCu ^ ((~BCa) & BCe);
Abo ^= Do;
BCa = ROL(Abo, 28);
Agu ^= Du;
BCe = ROL(Agu, 20);
Aka ^= Da;
BCi = ROL(Aka, 3);
Ame ^= De;
BCo = ROL(Ame, 45);
Asi ^= Di;
BCu = ROL(Asi, 61);
Ega = BCa ^ ((~BCe) & BCi);
Ege = BCe ^ ((~BCi) & BCo);
Egi = BCi ^ ((~BCo) & BCu);
Ego = BCo ^ ((~BCu) & BCa);
Egu = BCu ^ ((~BCa) & BCe);
Abe ^= De;
BCa = ROL(Abe, 1);
Agi ^= Di;
BCe = ROL(Agi, 6);
Ako ^= Do;
BCi = ROL(Ako, 25);
Amu ^= Du;
BCo = ROL(Amu, 8);
Asa ^= Da;
BCu = ROL(Asa, 18);
Eka = BCa ^ ((~BCe) & BCi);
Eke = BCe ^ ((~BCi) & BCo);
Eki = BCi ^ ((~BCo) & BCu);
Eko = BCo ^ ((~BCu) & BCa);
Eku = BCu ^ ((~BCa) & BCe);
Abu ^= Du;
BCa = ROL(Abu, 27);
Aga ^= Da;
BCe = ROL(Aga, 36);
Ake ^= De;
BCi = ROL(Ake, 10);
Ami ^= Di;
BCo = ROL(Ami, 15);
Aso ^= Do;
BCu = ROL(Aso, 56);
Ema = BCa ^ ((~BCe) & BCi);
Eme = BCe ^ ((~BCi) & BCo);
Emi = BCi ^ ((~BCo) & BCu);
Emo = BCo ^ ((~BCu) & BCa);
Emu = BCu ^ ((~BCa) & BCe);
Abi ^= Di;
BCa = ROL(Abi, 62);
Ago ^= Do;
BCe = ROL(Ago, 55);
Aku ^= Du;
BCi = ROL(Aku, 39);
Ama ^= Da;
BCo = ROL(Ama, 41);
Ase ^= De;
BCu = ROL(Ase, 2);
Esa = BCa ^ ((~BCe) & BCi);
Ese = BCe ^ ((~BCi) & BCo);
Esi = BCi ^ ((~BCo) & BCu);
Eso = BCo ^ ((~BCu) & BCa);
Esu = BCu ^ ((~BCa) & BCe);
// prepareTheta
BCa = Eba ^ Ega ^ Eka ^ Ema ^ Esa;
BCe = Ebe ^ Ege ^ Eke ^ Eme ^ Ese;
BCi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi;
BCo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso;
BCu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu;
// thetaRhoPiChiIotaPrepareTheta(round+1, E, A)
Da = BCu ^ ROL(BCe, 1);
De = BCa ^ ROL(BCi, 1);
Di = BCe ^ ROL(BCo, 1);
Do = BCi ^ ROL(BCu, 1);
Du = BCo ^ ROL(BCa, 1);
Eba ^= Da;
BCa = Eba;
Ege ^= De;
BCe = ROL(Ege, 44);
Eki ^= Di;
BCi = ROL(Eki, 43);
Emo ^= Do;
BCo = ROL(Emo, 21);
Esu ^= Du;
BCu = ROL(Esu, 14);
Aba = BCa ^ ((~BCe) & BCi);
Aba ^= KeccakF_RoundConstants[round + 1];
Abe = BCe ^ ((~BCi) & BCo);
Abi = BCi ^ ((~BCo) & BCu);
Abo = BCo ^ ((~BCu) & BCa);
Abu = BCu ^ ((~BCa) & BCe);
Ebo ^= Do;
BCa = ROL(Ebo, 28);
Egu ^= Du;
BCe = ROL(Egu, 20);
Eka ^= Da;
BCi = ROL(Eka, 3);
Eme ^= De;
BCo = ROL(Eme, 45);
Esi ^= Di;
BCu = ROL(Esi, 61);
Aga = BCa ^ ((~BCe) & BCi);
Age = BCe ^ ((~BCi) & BCo);
Agi = BCi ^ ((~BCo) & BCu);
Ago = BCo ^ ((~BCu) & BCa);
Agu = BCu ^ ((~BCa) & BCe);
Ebe ^= De;
BCa = ROL(Ebe, 1);
Egi ^= Di;
BCe = ROL(Egi, 6);
Eko ^= Do;
BCi = ROL(Eko, 25);
Emu ^= Du;
BCo = ROL(Emu, 8);
Esa ^= Da;
BCu = ROL(Esa, 18);
Aka = BCa ^ ((~BCe) & BCi);
Ake = BCe ^ ((~BCi) & BCo);
Aki = BCi ^ ((~BCo) & BCu);
Ako = BCo ^ ((~BCu) & BCa);
Aku = BCu ^ ((~BCa) & BCe);
Ebu ^= Du;
BCa = ROL(Ebu, 27);
Ega ^= Da;
BCe = ROL(Ega, 36);
Eke ^= De;
BCi = ROL(Eke, 10);
Emi ^= Di;
BCo = ROL(Emi, 15);
Eso ^= Do;
BCu = ROL(Eso, 56);
Ama = BCa ^ ((~BCe) & BCi);
Ame = BCe ^ ((~BCi) & BCo);
Ami = BCi ^ ((~BCo) & BCu);
Amo = BCo ^ ((~BCu) & BCa);
Amu = BCu ^ ((~BCa) & BCe);
Ebi ^= Di;
BCa = ROL(Ebi, 62);
Ego ^= Do;
BCe = ROL(Ego, 55);
Eku ^= Du;
BCi = ROL(Eku, 39);
Ema ^= Da;
BCo = ROL(Ema, 41);
Ese ^= De;
BCu = ROL(Ese, 2);
Asa = BCa ^ ((~BCe) & BCi);
Ase = BCe ^ ((~BCi) & BCo);
Asi = BCi ^ ((~BCo) & BCu);
Aso = BCo ^ ((~BCu) & BCa);
Asu = BCu ^ ((~BCa) & BCe);
}
// copyToState(state, A)
state[0] = Aba;
state[1] = Abe;
state[2] = Abi;
state[3] = Abo;
state[4] = Abu;
state[5] = Aga;
state[6] = Age;
state[7] = Agi;
state[8] = Ago;
state[9] = Agu;
state[10] = Aka;
state[11] = Ake;
state[12] = Aki;
state[13] = Ako;
state[14] = Aku;
state[15] = Ama;
state[16] = Ame;
state[17] = Ami;
state[18] = Amo;
state[19] = Amu;
state[20] = Asa;
state[21] = Ase;
state[22] = Asi;
state[23] = Aso;
state[24] = Asu;
}
/*************************************************
* Name: keccak_absorb
*
* Description: Absorb step of Keccak;
* non-incremental, starts by zeroeing the state.
*
* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
* - const uint8_t *m: pointer to input to be absorbed into s
* - size_t mlen: length of input in bytes
* - uint8_t p: domain-separation byte for different
* Keccak-derived functions
**************************************************/
static void keccak_absorb(uint64_t *s, uint32_t r, const uint8_t *m,
size_t mlen, uint8_t p) {
size_t i;
uint8_t t[200];
/* Zero state */
for (i = 0; i < 25; ++i) {
s[i] = 0;
}
while (mlen >= r) {
for (i = 0; i < r / 8; ++i) {
s[i] ^= load64(m + 8 * i);
}
KeccakF1600_StatePermute(s);
mlen -= r;
m += r;
}
for (i = 0; i < r; ++i) {
t[i] = 0;
}
for (i = 0; i < mlen; ++i) {
t[i] = m[i];
}
t[i] = p;
t[r - 1] |= 128;
for (i = 0; i < r / 8; ++i) {
s[i] ^= load64(t + 8 * i);
}
}
/*************************************************
* Name: keccak_squeezeblocks
*
* Description: Squeeze step of Keccak. Squeezes full blocks of r bytes each.
* Modifies the state. Can be called multiple times to keep
* squeezing, i.e., is incremental.
*
* Arguments: - uint8_t *h: pointer to output blocks
* - size_t nblocks: number of blocks to be
* squeezed (written to h)
* - uint64_t *s: pointer to input/output Keccak state
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
**************************************************/
static void keccak_squeezeblocks(uint8_t *h, size_t nblocks,
uint64_t *s, uint32_t r) {
while (nblocks > 0) {
KeccakF1600_StatePermute(s);
for (size_t i = 0; i < (r >> 3); i++) {
store64(h + 8 * i, s[i]);
}
h += r;
nblocks--;
}
}
/*************************************************
* Name: keccak_inc_init
*
* Description: Initializes the incremental Keccak state to zero.
*
* Arguments: - uint64_t *s_inc: pointer to input/output incremental state
* First 25 values represent Keccak state.
* 26th value represents either the number of absorbed bytes
* that have not been permuted, or not-yet-squeezed bytes.
**************************************************/
static void keccak_inc_init(uint64_t *s_inc) {
size_t i;
for (i = 0; i < 25; ++i) {
s_inc[i] = 0;
}
s_inc[25] = 0;
}
/*************************************************
* Name: keccak_inc_absorb
*
* Description: Incremental keccak absorb
* Preceded by keccak_inc_init, succeeded by keccak_inc_finalize
*
* Arguments: - uint64_t *s_inc: pointer to input/output incremental state
* First 25 values represent Keccak state.
* 26th value represents either the number of absorbed bytes
* that have not been permuted, or not-yet-squeezed bytes.
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
* - const uint8_t *m: pointer to input to be absorbed into s
* - size_t mlen: length of input in bytes
**************************************************/
static void keccak_inc_absorb(uint64_t *s_inc, uint32_t r, const uint8_t *m,
size_t mlen) {
size_t i;
/* Recall that s_inc[25] is the non-absorbed bytes xored into the state */
while (mlen + s_inc[25] >= r) {
for (i = 0; i < r - s_inc[25]; i++) {
/* Take the i'th byte from message
xor with the s_inc[25] + i'th byte of the state; little-endian */
s_inc[(s_inc[25] + i) >> 3] ^= (uint64_t)m[i] << (8 * ((s_inc[25] + i) & 0x07));
}
mlen -= (size_t)(r - s_inc[25]);
m += r - s_inc[25];
s_inc[25] = 0;
KeccakF1600_StatePermute(s_inc);
}
for (i = 0; i < mlen; i++) {
s_inc[(s_inc[25] + i) >> 3] ^= (uint64_t)m[i] << (8 * ((s_inc[25] + i) & 0x07));
}
s_inc[25] += mlen;
}
/*************************************************
* Name: keccak_inc_finalize
*
* Description: Finalizes Keccak absorb phase, prepares for squeezing
*
* Arguments: - uint64_t *s_inc: pointer to input/output incremental state
* First 25 values represent Keccak state.
* 26th value represents either the number of absorbed bytes
* that have not been permuted, or not-yet-squeezed bytes.
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
* - uint8_t p: domain-separation byte for different
* Keccak-derived functions
**************************************************/
static void keccak_inc_finalize(uint64_t *s_inc, uint32_t r, uint8_t p) {
/* After keccak_inc_absorb, we are guaranteed that s_inc[25] < r,
so we can always use one more byte for p in the current state. */
s_inc[s_inc[25] >> 3] ^= (uint64_t)p << (8 * (s_inc[25] & 0x07));
s_inc[(r - 1) >> 3] ^= (uint64_t)128 << (8 * ((r - 1) & 0x07));
s_inc[25] = 0;
}
/*************************************************
* Name: keccak_inc_squeeze
*
* Description: Incremental Keccak squeeze; can be called on byte-level
*
* Arguments: - uint8_t *h: pointer to output bytes
* - size_t outlen: number of bytes to be squeezed
* - uint64_t *s_inc: pointer to input/output incremental state
* First 25 values represent Keccak state.
* 26th value represents either the number of absorbed bytes
* that have not been permuted, or not-yet-squeezed bytes.
* - uint32_t r: rate in bytes (e.g., 168 for SHAKE128)
**************************************************/
static void keccak_inc_squeeze(uint8_t *h, size_t outlen,
uint64_t *s_inc, uint32_t r) {
size_t i;
/* First consume any bytes we still have sitting around */
for (i = 0; i < outlen && i < s_inc[25]; i++) {
/* There are s_inc[25] bytes left, so r - s_inc[25] is the first
available byte. We consume from there, i.e., up to r. */
h[i] = (uint8_t)(s_inc[(r - s_inc[25] + i) >> 3] >> (8 * ((r - s_inc[25] + i) & 0x07)));
}
h += i;
outlen -= i;
s_inc[25] -= i;
/* Then squeeze the remaining necessary blocks */
while (outlen > 0) {
KeccakF1600_StatePermute(s_inc);
for (i = 0; i < outlen && i < r; i++) {
h[i] = (uint8_t)(s_inc[i >> 3] >> (8 * (i & 0x07)));
}
h += i;
outlen -= i;
s_inc[25] = r - i;
}
}
void shake256_inc_init(uint64_t *s_inc) {
keccak_inc_init(s_inc);
}
void shake256_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen) {
keccak_inc_absorb(s_inc, SHAKE256_RATE, input, inlen);
}
void shake256_inc_finalize(uint64_t *s_inc) {
keccak_inc_finalize(s_inc, SHAKE256_RATE, 0x1F);
}
void shake256_inc_squeeze(uint8_t *output, size_t outlen, uint64_t *s_inc) {
keccak_inc_squeeze(output, outlen, s_inc, SHAKE256_RATE);
}
/*************************************************
* Name: shake256_absorb
*
* Description: Absorb step of the SHAKE256 XOF.
* non-incremental, starts by zeroeing the state.
*
* Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak state
* - const uint8_t *input: pointer to input to be absorbed
* into s
* - size_t inlen: length of input in bytes
**************************************************/
void shake256_absorb(uint64_t *s, const uint8_t *input, size_t inlen) {
keccak_absorb(s, SHAKE256_RATE, input, inlen, 0x1F);
}
/*************************************************
* Name: shake256_squeezeblocks
*
* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of
* SHAKE256_RATE bytes each. Modifies the state. Can be called
* multiple times to keep squeezing, i.e., is incremental.
*
* Arguments: - uint8_t *output: pointer to output blocks
* - size_t nblocks: number of blocks to be squeezed
* (written to output)
* - uint64_t *s: pointer to input/output Keccak state
**************************************************/
void shake256_squeezeblocks(uint8_t *output, size_t nblocks, uint64_t *s) {
keccak_squeezeblocks(output, nblocks, s, SHAKE256_RATE);
}
/*************************************************
* Name: shake256
*
* Description: SHAKE256 XOF with non-incremental API
*
* Arguments: - uint8_t *output: pointer to output
* - size_t outlen: requested output length in bytes
* - const uint8_t *input: pointer to input
* - size_t inlen: length of input in bytes
**************************************************/
void shake256(uint8_t *output, size_t outlen,
const uint8_t *input, size_t inlen) {
size_t nblocks = outlen / SHAKE256_RATE;
uint8_t t[SHAKE256_RATE];
uint64_t s[25];
shake256_absorb(s, input, inlen);
shake256_squeezeblocks(output, nblocks, s);
output += nblocks * SHAKE256_RATE;
outlen -= nblocks * SHAKE256_RATE;
if (outlen) {
shake256_squeezeblocks(t, 1, s);
for (size_t i = 0; i < outlen; ++i) {
output[i] = t[i];
}
}
}

View file

@ -0,0 +1,47 @@
#ifndef SPX_FIPS202_H
#define SPX_FIPS202_H
#include <stddef.h>
#include <stdint.h>
#define SHAKE128_RATE 168
#define SHAKE256_RATE 136
#define SHA3_256_RATE 136
#define SHA3_512_RATE 72
void shake128_absorb(uint64_t *s, const uint8_t *input, size_t inlen);
void shake128_squeezeblocks(uint8_t *output, size_t nblocks, uint64_t *s);
void shake128_inc_init(uint64_t *s_inc);
void shake128_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen);
void shake128_inc_finalize(uint64_t *s_inc);
void shake128_inc_squeeze(uint8_t *output, size_t outlen, uint64_t *s_inc);
void shake256_absorb(uint64_t *s, const uint8_t *input, size_t inlen);
void shake256_squeezeblocks(uint8_t *output, size_t nblocks, uint64_t *s);
void shake256_inc_init(uint64_t *s_inc);
void shake256_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen);
void shake256_inc_finalize(uint64_t *s_inc);
void shake256_inc_squeeze(uint8_t *output, size_t outlen, uint64_t *s_inc);
void shake128(uint8_t *output, size_t outlen,
const uint8_t *input, size_t inlen);
void shake256(uint8_t *output, size_t outlen,
const uint8_t *input, size_t inlen);
void sha3_256_inc_init(uint64_t *s_inc);
void sha3_256_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen);
void sha3_256_inc_finalize(uint8_t *output, uint64_t *s_inc);
void sha3_256(uint8_t *output, const uint8_t *input, size_t inlen);
void sha3_512_inc_init(uint64_t *s_inc);
void sha3_512_inc_absorb(uint64_t *s_inc, const uint8_t *input, size_t inlen);
void sha3_512_inc_finalize(uint8_t *output, uint64_t *s_inc);
void sha3_512(uint8_t *output, const uint8_t *input, size_t inlen);
#endif

View file

@ -0,0 +1,161 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "fors.h"
#include "utils.h"
#include "utilsx1.h"
#include "hash.h"
#include "thash.h"
#include "address.h"
static void fors_gen_sk(unsigned char *sk, const spx_ctx *ctx,
uint32_t fors_leaf_addr[8])
{
prf_addr(sk, ctx, fors_leaf_addr);
}
static void fors_sk_to_leaf(unsigned char *leaf, const unsigned char *sk,
const spx_ctx *ctx,
uint32_t fors_leaf_addr[8])
{
thash(leaf, sk, 1, ctx, fors_leaf_addr);
}
struct fors_gen_leaf_info {
uint32_t leaf_addrx[8];
};
static void fors_gen_leafx1(unsigned char *leaf,
const spx_ctx *ctx,
uint32_t addr_idx, void *info)
{
struct fors_gen_leaf_info *fors_info = info;
uint32_t *fors_leaf_addr = fors_info->leaf_addrx;
/* Only set the parts that the caller doesn't set */
set_tree_index(fors_leaf_addr, addr_idx);
set_type(fors_leaf_addr, SPX_ADDR_TYPE_FORSPRF);
fors_gen_sk(leaf, ctx, fors_leaf_addr);
set_type(fors_leaf_addr, SPX_ADDR_TYPE_FORSTREE);
fors_sk_to_leaf(leaf, leaf,
ctx, fors_leaf_addr);
}
/**
* Interprets m as SPX_FORS_HEIGHT-bit unsigned integers.
* Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits.
* Assumes indices has space for SPX_FORS_TREES integers.
*/
static void message_to_indices(uint32_t *indices, const unsigned char *m)
{
unsigned int i, j;
unsigned int offset = 0;
for (i = 0; i < SPX_FORS_TREES; i++) {
indices[i] = 0;
for (j = 0; j < SPX_FORS_HEIGHT; j++) {
indices[i] ^= ((m[offset >> 3] >> (offset & 0x7)) & 1u) << j;
offset++;
}
}
}
/**
* Signs a message m, deriving the secret key from sk_seed and the FTS address.
* Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits.
*/
void fors_sign(unsigned char *sig, unsigned char *pk,
const unsigned char *m,
const spx_ctx *ctx,
const uint32_t fors_addr[8])
{
uint32_t indices[SPX_FORS_TREES];
unsigned char roots[SPX_FORS_TREES * SPX_N];
uint32_t fors_tree_addr[8] = {0};
struct fors_gen_leaf_info fors_info = {0};
uint32_t *fors_leaf_addr = fors_info.leaf_addrx;
uint32_t fors_pk_addr[8] = {0};
uint32_t idx_offset;
unsigned int i;
copy_keypair_addr(fors_tree_addr, fors_addr);
copy_keypair_addr(fors_leaf_addr, fors_addr);
copy_keypair_addr(fors_pk_addr, fors_addr);
set_type(fors_pk_addr, SPX_ADDR_TYPE_FORSPK);
message_to_indices(indices, m);
for (i = 0; i < SPX_FORS_TREES; i++) {
idx_offset = i * (1 << SPX_FORS_HEIGHT);
set_tree_height(fors_tree_addr, 0);
set_tree_index(fors_tree_addr, indices[i] + idx_offset);
set_type(fors_tree_addr, SPX_ADDR_TYPE_FORSPRF);
/* Include the secret key part that produces the selected leaf node. */
fors_gen_sk(sig, ctx, fors_tree_addr);
set_type(fors_tree_addr, SPX_ADDR_TYPE_FORSTREE);
sig += SPX_N;
/* Compute the authentication path for this leaf node. */
treehashx1(roots + i*SPX_N, sig, ctx,
indices[i], idx_offset, SPX_FORS_HEIGHT, fors_gen_leafx1,
fors_tree_addr, &fors_info);
sig += SPX_N * SPX_FORS_HEIGHT;
}
/* Hash horizontally across all tree roots to derive the public key. */
thash(pk, roots, SPX_FORS_TREES, ctx, fors_pk_addr);
}
/**
* Derives the FORS public key from a signature.
* This can be used for verification by comparing to a known public key, or to
* subsequently verify a signature on the derived public key. The latter is the
* typical use-case when used as an FTS below an OTS in a hypertree.
* Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits.
*/
void fors_pk_from_sig(unsigned char *pk,
const unsigned char *sig, const unsigned char *m,
const spx_ctx* ctx,
const uint32_t fors_addr[8])
{
uint32_t indices[SPX_FORS_TREES];
unsigned char roots[SPX_FORS_TREES * SPX_N];
unsigned char leaf[SPX_N];
uint32_t fors_tree_addr[8] = {0};
uint32_t fors_pk_addr[8] = {0};
uint32_t idx_offset;
unsigned int i;
copy_keypair_addr(fors_tree_addr, fors_addr);
copy_keypair_addr(fors_pk_addr, fors_addr);
set_type(fors_tree_addr, SPX_ADDR_TYPE_FORSTREE);
set_type(fors_pk_addr, SPX_ADDR_TYPE_FORSPK);
message_to_indices(indices, m);
for (i = 0; i < SPX_FORS_TREES; i++) {
idx_offset = i * (1 << SPX_FORS_HEIGHT);
set_tree_height(fors_tree_addr, 0);
set_tree_index(fors_tree_addr, indices[i] + idx_offset);
/* Derive the leaf from the included secret key part. */
fors_sk_to_leaf(leaf, sig, ctx, fors_tree_addr);
sig += SPX_N;
/* Derive the corresponding root node of this tree. */
compute_root(roots + i*SPX_N, leaf, indices[i], idx_offset,
sig, SPX_FORS_HEIGHT, ctx, fors_tree_addr);
sig += SPX_N * SPX_FORS_HEIGHT;
}
/* Hash horizontally across all tree roots to derive the public key. */
thash(pk, roots, SPX_FORS_TREES, ctx, fors_pk_addr);
}

View file

@ -0,0 +1,32 @@
#ifndef SPX_FORS_H
#define SPX_FORS_H
#include <stdint.h>
#include "params.h"
#include "context.h"
/**
* Signs a message m, deriving the secret key from sk_seed and the FTS address.
* Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits.
*/
#define fors_sign SPX_NAMESPACE(fors_sign)
void fors_sign(unsigned char *sig, unsigned char *pk,
const unsigned char *m,
const spx_ctx* ctx,
const uint32_t fors_addr[8]);
/**
* Derives the FORS public key from a signature.
* This can be used for verification by comparing to a known public key, or to
* subsequently verify a signature on the derived public key. The latter is the
* typical use-case when used as an FTS below an OTS in a hypertree.
* Assumes m contains at least SPX_FORS_HEIGHT * SPX_FORS_TREES bits.
*/
#define fors_pk_from_sig SPX_NAMESPACE(fors_pk_from_sig)
void fors_pk_from_sig(unsigned char *pk,
const unsigned char *sig, const unsigned char *m,
const spx_ctx* ctx,
const uint32_t fors_addr[8]);
#endif

View file

@ -0,0 +1,27 @@
#ifndef SPX_HASH_H
#define SPX_HASH_H
#include <stdint.h>
#include "context.h"
#include "params.h"
#define initialize_hash_function SPX_NAMESPACE(initialize_hash_function)
void initialize_hash_function(spx_ctx *ctx);
#define prf_addr SPX_NAMESPACE(prf_addr)
void prf_addr(unsigned char *out, const spx_ctx *ctx,
const uint32_t addr[8]);
#define gen_message_random SPX_NAMESPACE(gen_message_random)
void gen_message_random(unsigned char *R, const unsigned char *sk_prf,
const unsigned char *optrand,
const unsigned char *m, unsigned long long mlen,
const spx_ctx *ctx);
#define hash_message SPX_NAMESPACE(hash_message)
void hash_message(unsigned char *digest, uint64_t *tree, uint32_t *leaf_idx,
const unsigned char *R, const unsigned char *pk,
const unsigned char *m, unsigned long long mlen,
const spx_ctx *ctx);
#endif

View file

@ -0,0 +1,197 @@
#include <stdint.h>
#include <string.h>
#include "address.h"
#include "utils.h"
#include "params.h"
#include "hash.h"
#include "sha2.h"
#if SPX_N >= 24
#define SPX_SHAX_OUTPUT_BYTES SPX_SHA512_OUTPUT_BYTES
#define SPX_SHAX_BLOCK_BYTES SPX_SHA512_BLOCK_BYTES
#define shaX_inc_init sha512_inc_init
#define shaX_inc_blocks sha512_inc_blocks
#define shaX_inc_finalize sha512_inc_finalize
#define shaX sha512
#define mgf1_X mgf1_512
#else
#define SPX_SHAX_OUTPUT_BYTES SPX_SHA256_OUTPUT_BYTES
#define SPX_SHAX_BLOCK_BYTES SPX_SHA256_BLOCK_BYTES
#define shaX_inc_init sha256_inc_init
#define shaX_inc_blocks sha256_inc_blocks
#define shaX_inc_finalize sha256_inc_finalize
#define shaX sha256
#define mgf1_X mgf1_256
#endif
/* For SHA, there is no immediate reason to initialize at the start,
so this function is an empty operation. */
void initialize_hash_function(spx_ctx *ctx)
{
seed_state(ctx);
}
/*
* Computes PRF(pk_seed, sk_seed, addr).
*/
void prf_addr(unsigned char *out, const spx_ctx *ctx,
const uint32_t addr[8])
{
uint8_t sha2_state[40];
unsigned char buf[SPX_SHA256_ADDR_BYTES + SPX_N];
unsigned char outbuf[SPX_SHA256_OUTPUT_BYTES];
/* Retrieve precomputed state containing pub_seed */
memcpy(sha2_state, ctx->state_seeded, 40 * sizeof(uint8_t));
/* Remainder: ADDR^c ‖ SK.seed */
memcpy(buf, addr, SPX_SHA256_ADDR_BYTES);
memcpy(buf + SPX_SHA256_ADDR_BYTES, ctx->sk_seed, SPX_N);
sha256_inc_finalize(outbuf, sha2_state, buf, SPX_SHA256_ADDR_BYTES + SPX_N);
memcpy(out, outbuf, SPX_N);
}
/**
* Computes the message-dependent randomness R, using a secret seed as a key
* for HMAC, and an optional randomization value prefixed to the message.
* This requires m to have at least SPX_SHAX_BLOCK_BYTES + SPX_N space
* available in front of the pointer, i.e. before the message to use for the
* prefix. This is necessary to prevent having to move the message around (and
* allocate memory for it).
*/
void gen_message_random(unsigned char *R, const unsigned char *sk_prf,
const unsigned char *optrand,
const unsigned char *m, unsigned long long mlen,
const spx_ctx *ctx)
{
(void)ctx;
unsigned char buf[SPX_SHAX_BLOCK_BYTES + SPX_SHAX_OUTPUT_BYTES];
uint8_t state[8 + SPX_SHAX_OUTPUT_BYTES];
int i;
#if SPX_N > SPX_SHAX_BLOCK_BYTES
#error "Currently only supports SPX_N of at most SPX_SHAX_BLOCK_BYTES"
#endif
/* This implements HMAC-SHA */
for (i = 0; i < SPX_N; i++) {
buf[i] = 0x36 ^ sk_prf[i];
}
memset(buf + SPX_N, 0x36, SPX_SHAX_BLOCK_BYTES - SPX_N);
shaX_inc_init(state);
shaX_inc_blocks(state, buf, 1);
memcpy(buf, optrand, SPX_N);
/* If optrand + message cannot fill up an entire block */
if (SPX_N + mlen < SPX_SHAX_BLOCK_BYTES) {
memcpy(buf + SPX_N, m, mlen);
shaX_inc_finalize(buf + SPX_SHAX_BLOCK_BYTES, state,
buf, mlen + SPX_N);
}
/* Otherwise first fill a block, so that finalize only uses the message */
else {
memcpy(buf + SPX_N, m, SPX_SHAX_BLOCK_BYTES - SPX_N);
shaX_inc_blocks(state, buf, 1);
m += SPX_SHAX_BLOCK_BYTES - SPX_N;
mlen -= SPX_SHAX_BLOCK_BYTES - SPX_N;
shaX_inc_finalize(buf + SPX_SHAX_BLOCK_BYTES, state, m, mlen);
}
for (i = 0; i < SPX_N; i++) {
buf[i] = 0x5c ^ sk_prf[i];
}
memset(buf + SPX_N, 0x5c, SPX_SHAX_BLOCK_BYTES - SPX_N);
shaX(buf, buf, SPX_SHAX_BLOCK_BYTES + SPX_SHAX_OUTPUT_BYTES);
memcpy(R, buf, SPX_N);
}
/**
* Computes the message hash using R, the public key, and the message.
* Outputs the message digest and the index of the leaf. The index is split in
* the tree index and the leaf index, for convenient copying to an address.
*/
void hash_message(unsigned char *digest, uint64_t *tree, uint32_t *leaf_idx,
const unsigned char *R, const unsigned char *pk,
const unsigned char *m, unsigned long long mlen,
const spx_ctx *ctx)
{
(void)ctx;
#define SPX_TREE_BITS (SPX_TREE_HEIGHT * (SPX_D - 1))
#define SPX_TREE_BYTES ((SPX_TREE_BITS + 7) / 8)
#define SPX_LEAF_BITS SPX_TREE_HEIGHT
#define SPX_LEAF_BYTES ((SPX_LEAF_BITS + 7) / 8)
#define SPX_DGST_BYTES (SPX_FORS_MSG_BYTES + SPX_TREE_BYTES + SPX_LEAF_BYTES)
unsigned char seed[2*SPX_N + SPX_SHAX_OUTPUT_BYTES];
/* Round to nearest multiple of SPX_SHAX_BLOCK_BYTES */
#if (SPX_SHAX_BLOCK_BYTES & (SPX_SHAX_BLOCK_BYTES-1)) != 0
#error "Assumes that SPX_SHAX_BLOCK_BYTES is a power of 2"
#endif
#define SPX_INBLOCKS (((SPX_N + SPX_PK_BYTES + SPX_SHAX_BLOCK_BYTES - 1) & \
-SPX_SHAX_BLOCK_BYTES) / SPX_SHAX_BLOCK_BYTES)
unsigned char inbuf[SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES];
unsigned char buf[SPX_DGST_BYTES];
unsigned char *bufp = buf;
uint8_t state[8 + SPX_SHAX_OUTPUT_BYTES];
shaX_inc_init(state);
// seed: SHA-X(R ‖ PK.seed ‖ PK.root ‖ M)
memcpy(inbuf, R, SPX_N);
memcpy(inbuf + SPX_N, pk, SPX_PK_BYTES);
/* If R + pk + message cannot fill up an entire block */
if (SPX_N + SPX_PK_BYTES + mlen < SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES) {
memcpy(inbuf + SPX_N + SPX_PK_BYTES, m, mlen);
shaX_inc_finalize(seed + 2*SPX_N, state, inbuf, SPX_N + SPX_PK_BYTES + mlen);
}
/* Otherwise first fill a block, so that finalize only uses the message */
else {
memcpy(inbuf + SPX_N + SPX_PK_BYTES, m,
SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES - SPX_N - SPX_PK_BYTES);
shaX_inc_blocks(state, inbuf, SPX_INBLOCKS);
m += SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES - SPX_N - SPX_PK_BYTES;
mlen -= SPX_INBLOCKS * SPX_SHAX_BLOCK_BYTES - SPX_N - SPX_PK_BYTES;
shaX_inc_finalize(seed + 2*SPX_N, state, m, mlen);
}
// H_msg: MGF1-SHA-X(R ‖ PK.seed ‖ seed)
memcpy(seed, R, SPX_N);
memcpy(seed + SPX_N, pk, SPX_N);
/* By doing this in two steps, we prevent hashing the message twice;
otherwise each iteration in MGF1 would hash the message again. */
mgf1_X(bufp, SPX_DGST_BYTES, seed, 2*SPX_N + SPX_SHAX_OUTPUT_BYTES);
memcpy(digest, bufp, SPX_FORS_MSG_BYTES);
bufp += SPX_FORS_MSG_BYTES;
#if SPX_TREE_BITS > 64
#error For given height and depth, 64 bits cannot represent all subtrees
#endif
if (SPX_D == 1) {
*tree = 0;
} else {
*tree = bytes_to_ull(bufp, SPX_TREE_BYTES);
*tree &= (~(uint64_t)0) >> (64 - SPX_TREE_BITS);
}
bufp += SPX_TREE_BYTES;
*leaf_idx = (uint32_t)bytes_to_ull(bufp, SPX_LEAF_BYTES);
*leaf_idx &= (~(uint32_t)0) >> (32 - SPX_LEAF_BITS);
}

View file

@ -0,0 +1,61 @@
#include <stdint.h>
#include <string.h>
#include "utils.h"
#include "utilsx1.h"
#include "wots.h"
#include "wotsx1.h"
#include "merkle.h"
#include "address.h"
#include "params.h"
/*
* This generates a Merkle signature (WOTS signature followed by the Merkle
* authentication path). This is in this file because most of the complexity
* is involved with the WOTS signature; the Merkle authentication path logic
* is mostly hidden in treehashx4
*/
void merkle_sign(uint8_t *sig, unsigned char *root,
const spx_ctx *ctx,
uint32_t wots_addr[8], uint32_t tree_addr[8],
uint32_t idx_leaf)
{
unsigned char *auth_path = sig + SPX_WOTS_BYTES;
struct leaf_info_x1 info = { 0 };
unsigned steps[ SPX_WOTS_LEN ];
info.wots_sig = sig;
chain_lengths(steps, root);
info.wots_steps = steps;
set_type(&tree_addr[0], SPX_ADDR_TYPE_HASHTREE);
set_type(&info.pk_addr[0], SPX_ADDR_TYPE_WOTSPK);
copy_subtree_addr(&info.leaf_addr[0], wots_addr);
copy_subtree_addr(&info.pk_addr[0], wots_addr);
info.wots_sign_leaf = idx_leaf;
treehashx1(root, auth_path, ctx,
idx_leaf, 0,
SPX_TREE_HEIGHT,
wots_gen_leafx1,
tree_addr, &info);
}
/* Compute root node of the top-most subtree. */
void merkle_gen_root(unsigned char *root, const spx_ctx *ctx)
{
/* We do not need the auth path in key generation, but it simplifies the
code to have just one treehash routine that computes both root and path
in one function. */
unsigned char auth_path[SPX_TREE_HEIGHT * SPX_N + SPX_WOTS_BYTES];
uint32_t top_tree_addr[8] = {0};
uint32_t wots_addr[8] = {0};
set_layer_addr(top_tree_addr, SPX_D - 1);
set_layer_addr(wots_addr, SPX_D - 1);
merkle_sign(auth_path, root, ctx,
wots_addr, top_tree_addr,
(uint32_t)~0 /* ~0 means "don't bother generating an auth path */ );
}

View file

@ -0,0 +1,18 @@
#if !defined( MERKLE_H_ )
#define MERKLE_H_
#include <stdint.h>
/* Generate a Merkle signature (WOTS signature followed by the Merkle */
/* authentication path) */
#define merkle_sign SPX_NAMESPACE(merkle_sign)
void merkle_sign(uint8_t *sig, unsigned char *root,
const spx_ctx* ctx,
uint32_t wots_addr[8], uint32_t tree_addr[8],
uint32_t idx_leaf);
/* Compute the root node of the top-most subtree. */
#define merkle_gen_root SPX_NAMESPACE(merkle_gen_root)
void merkle_gen_root(unsigned char *root, const spx_ctx* ctx);
#endif /* MERKLE_H_ */

View file

@ -0,0 +1,5 @@
#define str(s) #s
#define xstr(s) str(s)
#include xstr(params/params-PARAMS.h)

View file

@ -0,0 +1,85 @@
#ifndef SPX_PARAMS_H
#define SPX_PARAMS_H
#define SPX_NAMESPACE(s) SPX_##s
/* Hash output length in bytes. */
#define SPX_N 16
/* Height of the hypertree. */
#define SPX_FULL_HEIGHT 63
/* Number of subtree layer. */
#define SPX_D 7
/* FORS tree dimensions. */
#define SPX_FORS_HEIGHT 12
#define SPX_FORS_TREES 14
/* Winternitz parameter, */
#define SPX_WOTS_W 16
/* The hash function is defined by linking a different hash.c file, as opposed
to setting a #define constant. */
/* This is a SHA2-based parameter set, hence whether we use SHA-256
* exclusively or we use both SHA-256 and SHA-512 is controlled by
* the following #define */
#define SPX_SHA512 0 /* Use SHA-256 for all hashes */
/* For clarity */
#define SPX_ADDR_BYTES 32
/* WOTS parameters. */
#if SPX_WOTS_W == 256
#define SPX_WOTS_LOGW 8
#elif SPX_WOTS_W == 16
#define SPX_WOTS_LOGW 4
#else
#error SPX_WOTS_W assumed 16 or 256
#endif
#define SPX_WOTS_LEN1 (8 * SPX_N / SPX_WOTS_LOGW)
/* SPX_WOTS_LEN2 is floor(log(len_1 * (w - 1)) / log(w)) + 1; we precompute */
#if SPX_WOTS_W == 256
#if SPX_N <= 1
#define SPX_WOTS_LEN2 1
#elif SPX_N <= 256
#define SPX_WOTS_LEN2 2
#else
#error Did not precompute SPX_WOTS_LEN2 for n outside {2, .., 256}
#endif
#elif SPX_WOTS_W == 16
#if SPX_N <= 8
#define SPX_WOTS_LEN2 2
#elif SPX_N <= 136
#define SPX_WOTS_LEN2 3
#elif SPX_N <= 256
#define SPX_WOTS_LEN2 4
#else
#error Did not precompute SPX_WOTS_LEN2 for n outside {2, .., 256}
#endif
#endif
#define SPX_WOTS_LEN (SPX_WOTS_LEN1 + SPX_WOTS_LEN2)
#define SPX_WOTS_BYTES (SPX_WOTS_LEN * SPX_N)
#define SPX_WOTS_PK_BYTES SPX_WOTS_BYTES
/* Subtree size. */
#define SPX_TREE_HEIGHT (SPX_FULL_HEIGHT / SPX_D)
#if SPX_TREE_HEIGHT * SPX_D != SPX_FULL_HEIGHT
#error SPX_D should always divide SPX_FULL_HEIGHT
#endif
/* FORS parameters. */
#define SPX_FORS_MSG_BYTES ((SPX_FORS_HEIGHT * SPX_FORS_TREES + 7) / 8)
#define SPX_FORS_BYTES ((SPX_FORS_HEIGHT + 1) * SPX_FORS_TREES * SPX_N)
#define SPX_FORS_PK_BYTES SPX_N
/* Resulting SPX sizes. */
#define SPX_BYTES (SPX_N + SPX_FORS_BYTES + SPX_D * SPX_WOTS_BYTES +\
SPX_FULL_HEIGHT * SPX_N)
#define SPX_PK_BYTES (2 * SPX_N)
#define SPX_SK_BYTES (2 * SPX_N + SPX_PK_BYTES)
#include "../sha2_offsets.h"
#endif

View file

@ -0,0 +1,15 @@
#ifndef VEXTA_PQ_RANDOMBYTES_H
#define VEXTA_PQ_RANDOMBYTES_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* VEXTA: unified prototype. Definition lives in pq_randombytes.cpp and is
backed by Bitcoin's CSPRNG (GetStrongRandBytes). The upstream test-only
randombytes.c files were deleted — they must never ship in a wallet. */
void randombytes(uint8_t *out, size_t outlen);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,700 @@
/* Based on the public domain implementation in
* crypto_hash/sha512/ref/ from http://bench.cr.yp.to/supercop.html
* by D. J. Bernstein */
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "utils.h"
#include "sha2.h"
static uint32_t load_bigendian_32(const uint8_t *x) {
return (uint32_t)(x[3]) | (((uint32_t)(x[2])) << 8) |
(((uint32_t)(x[1])) << 16) | (((uint32_t)(x[0])) << 24);
}
static uint64_t load_bigendian_64(const uint8_t *x) {
return (uint64_t)(x[7]) | (((uint64_t)(x[6])) << 8) |
(((uint64_t)(x[5])) << 16) | (((uint64_t)(x[4])) << 24) |
(((uint64_t)(x[3])) << 32) | (((uint64_t)(x[2])) << 40) |
(((uint64_t)(x[1])) << 48) | (((uint64_t)(x[0])) << 56);
}
static void store_bigendian_32(uint8_t *x, uint64_t u) {
x[3] = (uint8_t) u;
u >>= 8;
x[2] = (uint8_t) u;
u >>= 8;
x[1] = (uint8_t) u;
u >>= 8;
x[0] = (uint8_t) u;
}
static void store_bigendian_64(uint8_t *x, uint64_t u) {
x[7] = (uint8_t) u;
u >>= 8;
x[6] = (uint8_t) u;
u >>= 8;
x[5] = (uint8_t) u;
u >>= 8;
x[4] = (uint8_t) u;
u >>= 8;
x[3] = (uint8_t) u;
u >>= 8;
x[2] = (uint8_t) u;
u >>= 8;
x[1] = (uint8_t) u;
u >>= 8;
x[0] = (uint8_t) u;
}
#define SHR(x, c) ((x) >> (c))
#define ROTR_32(x, c) (((x) >> (c)) | ((x) << (32 - (c))))
#define ROTR_64(x,c) (((x) >> (c)) | ((x) << (64 - (c))))
#define Ch(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
#define Maj(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define Sigma0_32(x) (ROTR_32(x, 2) ^ ROTR_32(x,13) ^ ROTR_32(x,22))
#define Sigma1_32(x) (ROTR_32(x, 6) ^ ROTR_32(x,11) ^ ROTR_32(x,25))
#define sigma0_32(x) (ROTR_32(x, 7) ^ ROTR_32(x,18) ^ SHR(x, 3))
#define sigma1_32(x) (ROTR_32(x,17) ^ ROTR_32(x,19) ^ SHR(x,10))
#define Sigma0_64(x) (ROTR_64(x,28) ^ ROTR_64(x,34) ^ ROTR_64(x,39))
#define Sigma1_64(x) (ROTR_64(x,14) ^ ROTR_64(x,18) ^ ROTR_64(x,41))
#define sigma0_64(x) (ROTR_64(x, 1) ^ ROTR_64(x, 8) ^ SHR(x,7))
#define sigma1_64(x) (ROTR_64(x,19) ^ ROTR_64(x,61) ^ SHR(x,6))
#define M_32(w0, w14, w9, w1) w0 = sigma1_32(w14) + (w9) + sigma0_32(w1) + (w0);
#define M_64(w0, w14, w9, w1) w0 = sigma1_64(w14) + (w9) + sigma0_64(w1) + (w0);
#define EXPAND_32 \
M_32(w0, w14, w9, w1) \
M_32(w1, w15, w10, w2) \
M_32(w2, w0, w11, w3) \
M_32(w3, w1, w12, w4) \
M_32(w4, w2, w13, w5) \
M_32(w5, w3, w14, w6) \
M_32(w6, w4, w15, w7) \
M_32(w7, w5, w0, w8) \
M_32(w8, w6, w1, w9) \
M_32(w9, w7, w2, w10) \
M_32(w10, w8, w3, w11) \
M_32(w11, w9, w4, w12) \
M_32(w12, w10, w5, w13) \
M_32(w13, w11, w6, w14) \
M_32(w14, w12, w7, w15) \
M_32(w15, w13, w8, w0)
#define EXPAND_64 \
M_64(w0 ,w14,w9 ,w1 ) \
M_64(w1 ,w15,w10,w2 ) \
M_64(w2 ,w0 ,w11,w3 ) \
M_64(w3 ,w1 ,w12,w4 ) \
M_64(w4 ,w2 ,w13,w5 ) \
M_64(w5 ,w3 ,w14,w6 ) \
M_64(w6 ,w4 ,w15,w7 ) \
M_64(w7 ,w5 ,w0 ,w8 ) \
M_64(w8 ,w6 ,w1 ,w9 ) \
M_64(w9 ,w7 ,w2 ,w10) \
M_64(w10,w8 ,w3 ,w11) \
M_64(w11,w9 ,w4 ,w12) \
M_64(w12,w10,w5 ,w13) \
M_64(w13,w11,w6 ,w14) \
M_64(w14,w12,w7 ,w15) \
M_64(w15,w13,w8 ,w0 )
#define F_32(w, k) \
T1 = h + Sigma1_32(e) + Ch(e, f, g) + (k) + (w); \
T2 = Sigma0_32(a) + Maj(a, b, c); \
h = g; \
g = f; \
f = e; \
e = d + T1; \
d = c; \
c = b; \
b = a; \
a = T1 + T2;
#define F_64(w,k) \
T1 = h + Sigma1_64(e) + Ch(e,f,g) + k + w; \
T2 = Sigma0_64(a) + Maj(a,b,c); \
h = g; \
g = f; \
f = e; \
e = d + T1; \
d = c; \
c = b; \
b = a; \
a = T1 + T2;
static size_t crypto_hashblocks_sha256(uint8_t *statebytes,
const uint8_t *in, size_t inlen) {
uint32_t state[8];
uint32_t a;
uint32_t b;
uint32_t c;
uint32_t d;
uint32_t e;
uint32_t f;
uint32_t g;
uint32_t h;
uint32_t T1;
uint32_t T2;
a = load_bigendian_32(statebytes + 0);
state[0] = a;
b = load_bigendian_32(statebytes + 4);
state[1] = b;
c = load_bigendian_32(statebytes + 8);
state[2] = c;
d = load_bigendian_32(statebytes + 12);
state[3] = d;
e = load_bigendian_32(statebytes + 16);
state[4] = e;
f = load_bigendian_32(statebytes + 20);
state[5] = f;
g = load_bigendian_32(statebytes + 24);
state[6] = g;
h = load_bigendian_32(statebytes + 28);
state[7] = h;
while (inlen >= 64) {
uint32_t w0 = load_bigendian_32(in + 0);
uint32_t w1 = load_bigendian_32(in + 4);
uint32_t w2 = load_bigendian_32(in + 8);
uint32_t w3 = load_bigendian_32(in + 12);
uint32_t w4 = load_bigendian_32(in + 16);
uint32_t w5 = load_bigendian_32(in + 20);
uint32_t w6 = load_bigendian_32(in + 24);
uint32_t w7 = load_bigendian_32(in + 28);
uint32_t w8 = load_bigendian_32(in + 32);
uint32_t w9 = load_bigendian_32(in + 36);
uint32_t w10 = load_bigendian_32(in + 40);
uint32_t w11 = load_bigendian_32(in + 44);
uint32_t w12 = load_bigendian_32(in + 48);
uint32_t w13 = load_bigendian_32(in + 52);
uint32_t w14 = load_bigendian_32(in + 56);
uint32_t w15 = load_bigendian_32(in + 60);
F_32(w0, 0x428a2f98)
F_32(w1, 0x71374491)
F_32(w2, 0xb5c0fbcf)
F_32(w3, 0xe9b5dba5)
F_32(w4, 0x3956c25b)
F_32(w5, 0x59f111f1)
F_32(w6, 0x923f82a4)
F_32(w7, 0xab1c5ed5)
F_32(w8, 0xd807aa98)
F_32(w9, 0x12835b01)
F_32(w10, 0x243185be)
F_32(w11, 0x550c7dc3)
F_32(w12, 0x72be5d74)
F_32(w13, 0x80deb1fe)
F_32(w14, 0x9bdc06a7)
F_32(w15, 0xc19bf174)
EXPAND_32
F_32(w0, 0xe49b69c1)
F_32(w1, 0xefbe4786)
F_32(w2, 0x0fc19dc6)
F_32(w3, 0x240ca1cc)
F_32(w4, 0x2de92c6f)
F_32(w5, 0x4a7484aa)
F_32(w6, 0x5cb0a9dc)
F_32(w7, 0x76f988da)
F_32(w8, 0x983e5152)
F_32(w9, 0xa831c66d)
F_32(w10, 0xb00327c8)
F_32(w11, 0xbf597fc7)
F_32(w12, 0xc6e00bf3)
F_32(w13, 0xd5a79147)
F_32(w14, 0x06ca6351)
F_32(w15, 0x14292967)
EXPAND_32
F_32(w0, 0x27b70a85)
F_32(w1, 0x2e1b2138)
F_32(w2, 0x4d2c6dfc)
F_32(w3, 0x53380d13)
F_32(w4, 0x650a7354)
F_32(w5, 0x766a0abb)
F_32(w6, 0x81c2c92e)
F_32(w7, 0x92722c85)
F_32(w8, 0xa2bfe8a1)
F_32(w9, 0xa81a664b)
F_32(w10, 0xc24b8b70)
F_32(w11, 0xc76c51a3)
F_32(w12, 0xd192e819)
F_32(w13, 0xd6990624)
F_32(w14, 0xf40e3585)
F_32(w15, 0x106aa070)
EXPAND_32
F_32(w0, 0x19a4c116)
F_32(w1, 0x1e376c08)
F_32(w2, 0x2748774c)
F_32(w3, 0x34b0bcb5)
F_32(w4, 0x391c0cb3)
F_32(w5, 0x4ed8aa4a)
F_32(w6, 0x5b9cca4f)
F_32(w7, 0x682e6ff3)
F_32(w8, 0x748f82ee)
F_32(w9, 0x78a5636f)
F_32(w10, 0x84c87814)
F_32(w11, 0x8cc70208)
F_32(w12, 0x90befffa)
F_32(w13, 0xa4506ceb)
F_32(w14, 0xbef9a3f7)
F_32(w15, 0xc67178f2)
a += state[0];
b += state[1];
c += state[2];
d += state[3];
e += state[4];
f += state[5];
g += state[6];
h += state[7];
state[0] = a;
state[1] = b;
state[2] = c;
state[3] = d;
state[4] = e;
state[5] = f;
state[6] = g;
state[7] = h;
in += 64;
inlen -= 64;
}
store_bigendian_32(statebytes + 0, state[0]);
store_bigendian_32(statebytes + 4, state[1]);
store_bigendian_32(statebytes + 8, state[2]);
store_bigendian_32(statebytes + 12, state[3]);
store_bigendian_32(statebytes + 16, state[4]);
store_bigendian_32(statebytes + 20, state[5]);
store_bigendian_32(statebytes + 24, state[6]);
store_bigendian_32(statebytes + 28, state[7]);
return inlen;
}
static int crypto_hashblocks_sha512(unsigned char *statebytes,const unsigned char *in,unsigned long long inlen)
{
uint64_t state[8];
uint64_t a;
uint64_t b;
uint64_t c;
uint64_t d;
uint64_t e;
uint64_t f;
uint64_t g;
uint64_t h;
uint64_t T1;
uint64_t T2;
a = load_bigendian_64(statebytes + 0); state[0] = a;
b = load_bigendian_64(statebytes + 8); state[1] = b;
c = load_bigendian_64(statebytes + 16); state[2] = c;
d = load_bigendian_64(statebytes + 24); state[3] = d;
e = load_bigendian_64(statebytes + 32); state[4] = e;
f = load_bigendian_64(statebytes + 40); state[5] = f;
g = load_bigendian_64(statebytes + 48); state[6] = g;
h = load_bigendian_64(statebytes + 56); state[7] = h;
while (inlen >= 128) {
uint64_t w0 = load_bigendian_64(in + 0);
uint64_t w1 = load_bigendian_64(in + 8);
uint64_t w2 = load_bigendian_64(in + 16);
uint64_t w3 = load_bigendian_64(in + 24);
uint64_t w4 = load_bigendian_64(in + 32);
uint64_t w5 = load_bigendian_64(in + 40);
uint64_t w6 = load_bigendian_64(in + 48);
uint64_t w7 = load_bigendian_64(in + 56);
uint64_t w8 = load_bigendian_64(in + 64);
uint64_t w9 = load_bigendian_64(in + 72);
uint64_t w10 = load_bigendian_64(in + 80);
uint64_t w11 = load_bigendian_64(in + 88);
uint64_t w12 = load_bigendian_64(in + 96);
uint64_t w13 = load_bigendian_64(in + 104);
uint64_t w14 = load_bigendian_64(in + 112);
uint64_t w15 = load_bigendian_64(in + 120);
F_64(w0 ,0x428a2f98d728ae22ULL)
F_64(w1 ,0x7137449123ef65cdULL)
F_64(w2 ,0xb5c0fbcfec4d3b2fULL)
F_64(w3 ,0xe9b5dba58189dbbcULL)
F_64(w4 ,0x3956c25bf348b538ULL)
F_64(w5 ,0x59f111f1b605d019ULL)
F_64(w6 ,0x923f82a4af194f9bULL)
F_64(w7 ,0xab1c5ed5da6d8118ULL)
F_64(w8 ,0xd807aa98a3030242ULL)
F_64(w9 ,0x12835b0145706fbeULL)
F_64(w10,0x243185be4ee4b28cULL)
F_64(w11,0x550c7dc3d5ffb4e2ULL)
F_64(w12,0x72be5d74f27b896fULL)
F_64(w13,0x80deb1fe3b1696b1ULL)
F_64(w14,0x9bdc06a725c71235ULL)
F_64(w15,0xc19bf174cf692694ULL)
EXPAND_64
F_64(w0 ,0xe49b69c19ef14ad2ULL)
F_64(w1 ,0xefbe4786384f25e3ULL)
F_64(w2 ,0x0fc19dc68b8cd5b5ULL)
F_64(w3 ,0x240ca1cc77ac9c65ULL)
F_64(w4 ,0x2de92c6f592b0275ULL)
F_64(w5 ,0x4a7484aa6ea6e483ULL)
F_64(w6 ,0x5cb0a9dcbd41fbd4ULL)
F_64(w7 ,0x76f988da831153b5ULL)
F_64(w8 ,0x983e5152ee66dfabULL)
F_64(w9 ,0xa831c66d2db43210ULL)
F_64(w10,0xb00327c898fb213fULL)
F_64(w11,0xbf597fc7beef0ee4ULL)
F_64(w12,0xc6e00bf33da88fc2ULL)
F_64(w13,0xd5a79147930aa725ULL)
F_64(w14,0x06ca6351e003826fULL)
F_64(w15,0x142929670a0e6e70ULL)
EXPAND_64
F_64(w0 ,0x27b70a8546d22ffcULL)
F_64(w1 ,0x2e1b21385c26c926ULL)
F_64(w2 ,0x4d2c6dfc5ac42aedULL)
F_64(w3 ,0x53380d139d95b3dfULL)
F_64(w4 ,0x650a73548baf63deULL)
F_64(w5 ,0x766a0abb3c77b2a8ULL)
F_64(w6 ,0x81c2c92e47edaee6ULL)
F_64(w7 ,0x92722c851482353bULL)
F_64(w8 ,0xa2bfe8a14cf10364ULL)
F_64(w9 ,0xa81a664bbc423001ULL)
F_64(w10,0xc24b8b70d0f89791ULL)
F_64(w11,0xc76c51a30654be30ULL)
F_64(w12,0xd192e819d6ef5218ULL)
F_64(w13,0xd69906245565a910ULL)
F_64(w14,0xf40e35855771202aULL)
F_64(w15,0x106aa07032bbd1b8ULL)
EXPAND_64
F_64(w0 ,0x19a4c116b8d2d0c8ULL)
F_64(w1 ,0x1e376c085141ab53ULL)
F_64(w2 ,0x2748774cdf8eeb99ULL)
F_64(w3 ,0x34b0bcb5e19b48a8ULL)
F_64(w4 ,0x391c0cb3c5c95a63ULL)
F_64(w5 ,0x4ed8aa4ae3418acbULL)
F_64(w6 ,0x5b9cca4f7763e373ULL)
F_64(w7 ,0x682e6ff3d6b2b8a3ULL)
F_64(w8 ,0x748f82ee5defb2fcULL)
F_64(w9 ,0x78a5636f43172f60ULL)
F_64(w10,0x84c87814a1f0ab72ULL)
F_64(w11,0x8cc702081a6439ecULL)
F_64(w12,0x90befffa23631e28ULL)
F_64(w13,0xa4506cebde82bde9ULL)
F_64(w14,0xbef9a3f7b2c67915ULL)
F_64(w15,0xc67178f2e372532bULL)
EXPAND_64
F_64(w0 ,0xca273eceea26619cULL)
F_64(w1 ,0xd186b8c721c0c207ULL)
F_64(w2 ,0xeada7dd6cde0eb1eULL)
F_64(w3 ,0xf57d4f7fee6ed178ULL)
F_64(w4 ,0x06f067aa72176fbaULL)
F_64(w5 ,0x0a637dc5a2c898a6ULL)
F_64(w6 ,0x113f9804bef90daeULL)
F_64(w7 ,0x1b710b35131c471bULL)
F_64(w8 ,0x28db77f523047d84ULL)
F_64(w9 ,0x32caab7b40c72493ULL)
F_64(w10,0x3c9ebe0a15c9bebcULL)
F_64(w11,0x431d67c49c100d4cULL)
F_64(w12,0x4cc5d4becb3e42b6ULL)
F_64(w13,0x597f299cfc657e2aULL)
F_64(w14,0x5fcb6fab3ad6faecULL)
F_64(w15,0x6c44198c4a475817ULL)
a += state[0];
b += state[1];
c += state[2];
d += state[3];
e += state[4];
f += state[5];
g += state[6];
h += state[7];
state[0] = a;
state[1] = b;
state[2] = c;
state[3] = d;
state[4] = e;
state[5] = f;
state[6] = g;
state[7] = h;
in += 128;
inlen -= 128;
}
store_bigendian_64(statebytes + 0,state[0]);
store_bigendian_64(statebytes + 8,state[1]);
store_bigendian_64(statebytes + 16,state[2]);
store_bigendian_64(statebytes + 24,state[3]);
store_bigendian_64(statebytes + 32,state[4]);
store_bigendian_64(statebytes + 40,state[5]);
store_bigendian_64(statebytes + 48,state[6]);
store_bigendian_64(statebytes + 56,state[7]);
return inlen;
}
static const uint8_t iv_256[32] = {
0x6a, 0x09, 0xe6, 0x67, 0xbb, 0x67, 0xae, 0x85,
0x3c, 0x6e, 0xf3, 0x72, 0xa5, 0x4f, 0xf5, 0x3a,
0x51, 0x0e, 0x52, 0x7f, 0x9b, 0x05, 0x68, 0x8c,
0x1f, 0x83, 0xd9, 0xab, 0x5b, 0xe0, 0xcd, 0x19
};
static const uint8_t iv_512[64] = {
0x6a, 0x09, 0xe6, 0x67, 0xf3, 0xbc, 0xc9, 0x08, 0xbb, 0x67, 0xae,
0x85, 0x84, 0xca, 0xa7, 0x3b, 0x3c, 0x6e, 0xf3, 0x72, 0xfe, 0x94,
0xf8, 0x2b, 0xa5, 0x4f, 0xf5, 0x3a, 0x5f, 0x1d, 0x36, 0xf1, 0x51,
0x0e, 0x52, 0x7f, 0xad, 0xe6, 0x82, 0xd1, 0x9b, 0x05, 0x68, 0x8c,
0x2b, 0x3e, 0x6c, 0x1f, 0x1f, 0x83, 0xd9, 0xab, 0xfb, 0x41, 0xbd,
0x6b, 0x5b, 0xe0, 0xcd, 0x19, 0x13, 0x7e, 0x21, 0x79
};
void sha256_inc_init(uint8_t *state) {
for (size_t i = 0; i < 32; ++i) {
state[i] = iv_256[i];
}
for (size_t i = 32; i < 40; ++i) {
state[i] = 0;
}
}
void sha512_inc_init(uint8_t *state) {
for (size_t i = 0; i < 64; ++i) {
state[i] = iv_512[i];
}
for (size_t i = 64; i < 72; ++i) {
state[i] = 0;
}
}
void sha256_inc_blocks(uint8_t *state, const uint8_t *in, size_t inblocks) {
uint64_t bytes = load_bigendian_64(state + 32);
crypto_hashblocks_sha256(state, in, 64 * inblocks);
bytes += 64 * inblocks;
store_bigendian_64(state + 32, bytes);
}
void sha512_inc_blocks(uint8_t *state, const uint8_t *in, size_t inblocks) {
uint64_t bytes = load_bigendian_64(state + 64);
crypto_hashblocks_sha512(state, in, 128 * inblocks);
bytes += 128 * inblocks;
store_bigendian_64(state + 64, bytes);
}
void sha256_inc_finalize(uint8_t *out, uint8_t *state, const uint8_t *in, size_t inlen) {
uint8_t padded[128];
uint64_t bytes = load_bigendian_64(state + 32) + inlen;
crypto_hashblocks_sha256(state, in, inlen);
in += inlen;
inlen &= 63;
in -= inlen;
for (size_t i = 0; i < inlen; ++i) {
padded[i] = in[i];
}
padded[inlen] = 0x80;
if (inlen < 56) {
for (size_t i = inlen + 1; i < 56; ++i) {
padded[i] = 0;
}
padded[56] = (uint8_t) (bytes >> 53);
padded[57] = (uint8_t) (bytes >> 45);
padded[58] = (uint8_t) (bytes >> 37);
padded[59] = (uint8_t) (bytes >> 29);
padded[60] = (uint8_t) (bytes >> 21);
padded[61] = (uint8_t) (bytes >> 13);
padded[62] = (uint8_t) (bytes >> 5);
padded[63] = (uint8_t) (bytes << 3);
crypto_hashblocks_sha256(state, padded, 64);
} else {
for (size_t i = inlen + 1; i < 120; ++i) {
padded[i] = 0;
}
padded[120] = (uint8_t) (bytes >> 53);
padded[121] = (uint8_t) (bytes >> 45);
padded[122] = (uint8_t) (bytes >> 37);
padded[123] = (uint8_t) (bytes >> 29);
padded[124] = (uint8_t) (bytes >> 21);
padded[125] = (uint8_t) (bytes >> 13);
padded[126] = (uint8_t) (bytes >> 5);
padded[127] = (uint8_t) (bytes << 3);
crypto_hashblocks_sha256(state, padded, 128);
}
for (size_t i = 0; i < 32; ++i) {
out[i] = state[i];
}
}
void sha512_inc_finalize(uint8_t *out, uint8_t *state, const uint8_t *in, size_t inlen) {
uint8_t padded[256];
uint64_t bytes = load_bigendian_64(state + 64) + inlen;
crypto_hashblocks_sha512(state, in, inlen);
in += inlen;
inlen &= 127;
in -= inlen;
for (size_t i = 0; i < inlen; ++i) {
padded[i] = in[i];
}
padded[inlen] = 0x80;
if (inlen < 112) {
for (size_t i = inlen + 1; i < 119; ++i) {
padded[i] = 0;
}
padded[119] = (uint8_t) (bytes >> 61);
padded[120] = (uint8_t) (bytes >> 53);
padded[121] = (uint8_t) (bytes >> 45);
padded[122] = (uint8_t) (bytes >> 37);
padded[123] = (uint8_t) (bytes >> 29);
padded[124] = (uint8_t) (bytes >> 21);
padded[125] = (uint8_t) (bytes >> 13);
padded[126] = (uint8_t) (bytes >> 5);
padded[127] = (uint8_t) (bytes << 3);
crypto_hashblocks_sha512(state, padded, 128);
} else {
for (size_t i = inlen + 1; i < 247; ++i) {
padded[i] = 0;
}
padded[247] = (uint8_t) (bytes >> 61);
padded[248] = (uint8_t) (bytes >> 53);
padded[249] = (uint8_t) (bytes >> 45);
padded[250] = (uint8_t) (bytes >> 37);
padded[251] = (uint8_t) (bytes >> 29);
padded[252] = (uint8_t) (bytes >> 21);
padded[253] = (uint8_t) (bytes >> 13);
padded[254] = (uint8_t) (bytes >> 5);
padded[255] = (uint8_t) (bytes << 3);
crypto_hashblocks_sha512(state, padded, 256);
}
for (size_t i = 0; i < 64; ++i) {
out[i] = state[i];
}
}
void sha256(uint8_t *out, const uint8_t *in, size_t inlen) {
uint8_t state[40];
sha256_inc_init(state);
sha256_inc_finalize(out, state, in, inlen);
}
void sha512(uint8_t *out, const uint8_t *in, size_t inlen) {
uint8_t state[72];
sha512_inc_init(state);
sha512_inc_finalize(out, state, in, inlen);
}
/**
* mgf1 function based on the SHA-256 hash function
* Note that inlen should be sufficiently small that it still allows for
* an array to be allocated on the stack. Typically 'in' is merely a seed.
* Outputs outlen number of bytes
*/
void mgf1_256(unsigned char *out, unsigned long outlen,
const unsigned char *in, unsigned long inlen)
{
SPX_VLA(uint8_t, inbuf, inlen+4);
unsigned char outbuf[SPX_SHA256_OUTPUT_BYTES];
unsigned long i;
memcpy(inbuf, in, inlen);
/* While we can fit in at least another full block of SHA256 output.. */
for (i = 0; (i+1)*SPX_SHA256_OUTPUT_BYTES <= outlen; i++) {
u32_to_bytes(inbuf + inlen, i);
sha256(out, inbuf, inlen + 4);
out += SPX_SHA256_OUTPUT_BYTES;
}
/* Until we cannot anymore, and we fill the remainder. */
if (outlen > i*SPX_SHA256_OUTPUT_BYTES) {
u32_to_bytes(inbuf + inlen, i);
sha256(outbuf, inbuf, inlen + 4);
memcpy(out, outbuf, outlen - i*SPX_SHA256_OUTPUT_BYTES);
}
}
/*
* mgf1 function based on the SHA-512 hash function
*/
void mgf1_512(unsigned char *out, unsigned long outlen,
const unsigned char *in, unsigned long inlen)
{
SPX_VLA(uint8_t, inbuf, inlen+4);
unsigned char outbuf[SPX_SHA512_OUTPUT_BYTES];
unsigned long i;
memcpy(inbuf, in, inlen);
/* While we can fit in at least another full block of SHA512 output.. */
for (i = 0; (i+1)*SPX_SHA512_OUTPUT_BYTES <= outlen; i++) {
u32_to_bytes(inbuf + inlen, i);
sha512(out, inbuf, inlen + 4);
out += SPX_SHA512_OUTPUT_BYTES;
}
/* Until we cannot anymore, and we fill the remainder. */
if (outlen > i*SPX_SHA512_OUTPUT_BYTES) {
u32_to_bytes(inbuf + inlen, i);
sha512(outbuf, inbuf, inlen + 4);
memcpy(out, outbuf, outlen - i*SPX_SHA512_OUTPUT_BYTES);
}
}
/**
* Absorb the constant pub_seed using one round of the compression function
* This initializes state_seeded and state_seeded_512, which can then be
* reused in thash
**/
void seed_state(spx_ctx *ctx) {
uint8_t block[SPX_SHA512_BLOCK_BYTES];
size_t i;
for (i = 0; i < SPX_N; ++i) {
block[i] = ctx->pub_seed[i];
}
for (i = SPX_N; i < SPX_SHA512_BLOCK_BYTES; ++i) {
block[i] = 0;
}
/* block has been properly initialized for both SHA-256 and SHA-512 */
sha256_inc_init(ctx->state_seeded);
sha256_inc_blocks(ctx->state_seeded, block, 1);
#if SPX_SHA512
sha512_inc_init(ctx->state_seeded_512);
sha512_inc_blocks(ctx->state_seeded_512, block, 1);
#endif
}

View file

@ -0,0 +1,43 @@
#ifndef SPX_SHA2_H
#define SPX_SHA2_H
#include "params.h"
#define SPX_SHA256_BLOCK_BYTES 64
#define SPX_SHA256_OUTPUT_BYTES 32 /* This does not necessarily equal SPX_N */
#define SPX_SHA512_BLOCK_BYTES 128
#define SPX_SHA512_OUTPUT_BYTES 64
#if SPX_SHA256_OUTPUT_BYTES < SPX_N
#error Linking against SHA-256 with N larger than 32 bytes is not supported
#endif
#define SPX_SHA256_ADDR_BYTES 22
#include <stddef.h>
#include <stdint.h>
void sha256_inc_init(uint8_t *state);
void sha256_inc_blocks(uint8_t *state, const uint8_t *in, size_t inblocks);
void sha256_inc_finalize(uint8_t *out, uint8_t *state, const uint8_t *in, size_t inlen);
void sha256(uint8_t *out, const uint8_t *in, size_t inlen);
void sha512_inc_init(uint8_t *state);
void sha512_inc_blocks(uint8_t *state, const uint8_t *in, size_t inblocks);
void sha512_inc_finalize(uint8_t *out, uint8_t *state, const uint8_t *in, size_t inlen);
void sha512(uint8_t *out, const uint8_t *in, size_t inlen);
#define mgf1_256 SPX_NAMESPACE(mgf1_256)
void mgf1_256(unsigned char *out, unsigned long outlen,
const unsigned char *in, unsigned long inlen);
#define mgf1_512 SPX_NAMESPACE(mgf1_512)
void mgf1_512(unsigned char *out, unsigned long outlen,
const unsigned char *in, unsigned long inlen);
#define seed_state SPX_NAMESPACE(seed_state)
void seed_state(spx_ctx *ctx);
#endif

View file

@ -0,0 +1,20 @@
#ifndef SHA2_OFFSETS_H_
#define SHA2_OFFSETS_H_
/*
* Offsets of various fields in the address structure when we use SHA2 as
* the Sphincs+ hash function
*/
#define SPX_OFFSET_LAYER 0 /* The byte used to specify the Merkle tree layer */
#define SPX_OFFSET_TREE 1 /* The start of the 8 byte field used to specify the tree */
#define SPX_OFFSET_TYPE 9 /* The byte used to specify the hash type (reason) */
#define SPX_OFFSET_KP_ADDR 10 /* The start of the 4 byte field used to specify the key pair address */
#define SPX_OFFSET_CHAIN_ADDR 17 /* The byte used to specify the chain address (which Winternitz chain) */
#define SPX_OFFSET_HASH_ADDR 21 /* The byte used to specify the hash address (where in the Winternitz chain) */
#define SPX_OFFSET_TREE_HGT 17 /* The byte used to specify the height of this node in the FORS or Merkle tree */
#define SPX_OFFSET_TREE_INDEX 18 /* The start of the 4 byte field used to specify the node in the FORS or Merkle tree */
#define SPX_SHA2 1
#endif /* SHA2_OFFSETS_H_ */

View file

@ -0,0 +1,293 @@
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include "api.h"
#include "params.h"
#include "wots.h"
#include "fors.h"
#include "hash.h"
#include "thash.h"
#include "address.h"
#include "randombytes.h"
#include "utils.h"
#include "merkle.h"
#ifndef VEXTA_PQ_VERIFY_ONLY
/*
* Returns the length of a secret key, in bytes
*/
unsigned long long crypto_sign_secretkeybytes(void)
{
return CRYPTO_SECRETKEYBYTES;
}
/*
* Returns the length of a public key, in bytes
*/
unsigned long long crypto_sign_publickeybytes(void)
{
return CRYPTO_PUBLICKEYBYTES;
}
/*
* Returns the length of a signature, in bytes
*/
unsigned long long crypto_sign_bytes(void)
{
return CRYPTO_BYTES;
}
/*
* Returns the length of the seed required to generate a key pair, in bytes
*/
unsigned long long crypto_sign_seedbytes(void)
{
return CRYPTO_SEEDBYTES;
}
/*
* Generates an SPX key pair given a seed of length
* Format sk: [SK_SEED || SK_PRF || PUB_SEED || root]
* Format pk: [PUB_SEED || root]
*/
int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk,
const unsigned char *seed)
{
spx_ctx ctx;
/* Initialize SK_SEED, SK_PRF and PUB_SEED from seed. */
memcpy(sk, seed, CRYPTO_SEEDBYTES);
memcpy(pk, sk + 2*SPX_N, SPX_N);
memcpy(ctx.pub_seed, pk, SPX_N);
memcpy(ctx.sk_seed, sk, SPX_N);
/* This hook allows the hash function instantiation to do whatever
preparation or computation it needs, based on the public seed. */
initialize_hash_function(&ctx);
/* Compute root node of the top-most subtree. */
merkle_gen_root(sk + 3*SPX_N, &ctx);
memcpy(pk + SPX_N, sk + 3*SPX_N, SPX_N);
return 0;
}
/*
* Generates an SPX key pair.
* Format sk: [SK_SEED || SK_PRF || PUB_SEED || root]
* Format pk: [PUB_SEED || root]
*/
int crypto_sign_keypair(unsigned char *pk, unsigned char *sk)
{
unsigned char seed[CRYPTO_SEEDBYTES];
randombytes(seed, CRYPTO_SEEDBYTES);
crypto_sign_seed_keypair(pk, sk, seed);
return 0;
}
/**
* Returns an array containing a detached signature.
*/
int crypto_sign_signature(uint8_t *sig, size_t *siglen,
const uint8_t *m, size_t mlen, const uint8_t *sk)
{
spx_ctx ctx;
const unsigned char *sk_prf = sk + SPX_N;
const unsigned char *pk = sk + 2*SPX_N;
unsigned char optrand[SPX_N];
unsigned char mhash[SPX_FORS_MSG_BYTES];
unsigned char root[SPX_N];
uint32_t i;
uint64_t tree;
uint32_t idx_leaf;
uint32_t wots_addr[8] = {0};
uint32_t tree_addr[8] = {0};
memcpy(ctx.sk_seed, sk, SPX_N);
memcpy(ctx.pub_seed, pk, SPX_N);
/* This hook allows the hash function instantiation to do whatever
preparation or computation it needs, based on the public seed. */
initialize_hash_function(&ctx);
set_type(wots_addr, SPX_ADDR_TYPE_WOTS);
set_type(tree_addr, SPX_ADDR_TYPE_HASHTREE);
/* Optionally, signing can be made non-deterministic using optrand.
This can help counter side-channel attacks that would benefit from
getting a large number of traces when the signer uses the same nodes. */
randombytes(optrand, SPX_N);
/* Compute the digest randomization value. */
gen_message_random(sig, sk_prf, optrand, m, mlen, &ctx);
/* Derive the message digest and leaf index from R, PK and M. */
hash_message(mhash, &tree, &idx_leaf, sig, pk, m, mlen, &ctx);
sig += SPX_N;
set_tree_addr(wots_addr, tree);
set_keypair_addr(wots_addr, idx_leaf);
/* Sign the message hash using FORS. */
fors_sign(sig, root, mhash, &ctx, wots_addr);
sig += SPX_FORS_BYTES;
for (i = 0; i < SPX_D; i++) {
set_layer_addr(tree_addr, i);
set_tree_addr(tree_addr, tree);
copy_subtree_addr(wots_addr, tree_addr);
set_keypair_addr(wots_addr, idx_leaf);
merkle_sign(sig, root, &ctx, wots_addr, tree_addr, idx_leaf);
sig += SPX_WOTS_BYTES + SPX_TREE_HEIGHT * SPX_N;
/* Update the indices for the next layer. */
idx_leaf = (tree & ((1 << SPX_TREE_HEIGHT)-1));
tree = tree >> SPX_TREE_HEIGHT;
}
*siglen = SPX_BYTES;
return 0;
}
#endif // !VEXTA_PQ_VERIFY_ONLY
/**
* Verifies a detached signature and message under a given public key.
*/
int crypto_sign_verify(const uint8_t *sig, size_t siglen,
const uint8_t *m, size_t mlen, const uint8_t *pk)
{
spx_ctx ctx;
const unsigned char *pub_root = pk + SPX_N;
unsigned char mhash[SPX_FORS_MSG_BYTES];
unsigned char wots_pk[SPX_WOTS_BYTES];
unsigned char root[SPX_N];
unsigned char leaf[SPX_N];
unsigned int i;
uint64_t tree;
uint32_t idx_leaf;
uint32_t wots_addr[8] = {0};
uint32_t tree_addr[8] = {0};
uint32_t wots_pk_addr[8] = {0};
if (siglen != SPX_BYTES) {
return -1;
}
memcpy(ctx.pub_seed, pk, SPX_N);
/* This hook allows the hash function instantiation to do whatever
preparation or computation it needs, based on the public seed. */
initialize_hash_function(&ctx);
set_type(wots_addr, SPX_ADDR_TYPE_WOTS);
set_type(tree_addr, SPX_ADDR_TYPE_HASHTREE);
set_type(wots_pk_addr, SPX_ADDR_TYPE_WOTSPK);
/* Derive the message digest and leaf index from R || PK || M. */
/* The additional SPX_N is a result of the hash domain separator. */
hash_message(mhash, &tree, &idx_leaf, sig, pk, m, mlen, &ctx);
sig += SPX_N;
/* Layer correctly defaults to 0, so no need to set_layer_addr */
set_tree_addr(wots_addr, tree);
set_keypair_addr(wots_addr, idx_leaf);
fors_pk_from_sig(root, sig, mhash, &ctx, wots_addr);
sig += SPX_FORS_BYTES;
/* For each subtree.. */
for (i = 0; i < SPX_D; i++) {
set_layer_addr(tree_addr, i);
set_tree_addr(tree_addr, tree);
copy_subtree_addr(wots_addr, tree_addr);
set_keypair_addr(wots_addr, idx_leaf);
copy_keypair_addr(wots_pk_addr, wots_addr);
/* The WOTS public key is only correct if the signature was correct. */
/* Initially, root is the FORS pk, but on subsequent iterations it is
the root of the subtree below the currently processed subtree. */
wots_pk_from_sig(wots_pk, sig, root, &ctx, wots_addr);
sig += SPX_WOTS_BYTES;
/* Compute the leaf node using the WOTS public key. */
thash(leaf, wots_pk, SPX_WOTS_LEN, &ctx, wots_pk_addr);
/* Compute the root node of this subtree. */
compute_root(root, leaf, idx_leaf, 0, sig, SPX_TREE_HEIGHT,
&ctx, tree_addr);
sig += SPX_TREE_HEIGHT * SPX_N;
/* Update the indices for the next layer. */
idx_leaf = (tree & ((1 << SPX_TREE_HEIGHT)-1));
tree = tree >> SPX_TREE_HEIGHT;
}
/* Check if the root node equals the root node in the public key. */
if (memcmp(root, pub_root, SPX_N)) {
return -1;
}
return 0;
}
#ifndef VEXTA_PQ_VERIFY_ONLY
/**
* Returns an array containing the signature followed by the message.
*/
int crypto_sign(unsigned char *sm, unsigned long long *smlen,
const unsigned char *m, unsigned long long mlen,
const unsigned char *sk)
{
size_t siglen;
crypto_sign_signature(sm, &siglen, m, (size_t)mlen, sk);
memmove(sm + SPX_BYTES, m, mlen);
*smlen = siglen + mlen;
return 0;
}
#endif // !VEXTA_PQ_VERIFY_ONLY
/**
* Verifies a given signature-message pair under a given public key.
*/
int crypto_sign_open(unsigned char *m, unsigned long long *mlen,
const unsigned char *sm, unsigned long long smlen,
const unsigned char *pk)
{
/* The API caller does not necessarily know what size a signature should be
but SPHINCS+ signatures are always exactly SPX_BYTES. */
if (smlen < SPX_BYTES) {
memset(m, 0, smlen);
*mlen = 0;
return -1;
}
*mlen = smlen - SPX_BYTES;
if (crypto_sign_verify(sm, SPX_BYTES, sm + SPX_BYTES, (size_t)*mlen, pk)) {
memset(m, 0, smlen);
*mlen = 0;
return -1;
}
/* If verification was successful, move the message to the right place. */
memmove(m, sm + SPX_BYTES, *mlen);
return 0;
}

View file

@ -0,0 +1,13 @@
#ifndef SPX_THASH_H
#define SPX_THASH_H
#include "context.h"
#include "params.h"
#include <stdint.h>
#define thash SPX_NAMESPACE(thash)
void thash(unsigned char *out, const unsigned char *in, unsigned int inblocks,
const spx_ctx *ctx, uint32_t addr[8]);
#endif

View file

@ -0,0 +1,59 @@
#include <stdint.h>
#include <string.h>
#include "thash.h"
#include "address.h"
#include "params.h"
#include "utils.h"
#include "sha2.h"
#if SPX_SHA512
static void thash_512(unsigned char *out, const unsigned char *in, unsigned int inblocks,
const spx_ctx *ctx, uint32_t addr[8]);
#endif
/**
* Takes an array of inblocks concatenated arrays of SPX_N bytes.
*/
void thash(unsigned char *out, const unsigned char *in, unsigned int inblocks,
const spx_ctx *ctx, uint32_t addr[8])
{
#if SPX_SHA512
if (inblocks > 1) {
thash_512(out, in, inblocks, ctx, addr);
return;
}
#endif
unsigned char outbuf[SPX_SHA256_OUTPUT_BYTES];
uint8_t sha2_state[40];
SPX_VLA(uint8_t, buf, SPX_SHA256_ADDR_BYTES + inblocks*SPX_N);
/* Retrieve precomputed state containing pub_seed */
memcpy(sha2_state, ctx->state_seeded, 40 * sizeof(uint8_t));
memcpy(buf, addr, SPX_SHA256_ADDR_BYTES);
memcpy(buf + SPX_SHA256_ADDR_BYTES, in, inblocks * SPX_N);
sha256_inc_finalize(outbuf, sha2_state, buf, SPX_SHA256_ADDR_BYTES + inblocks*SPX_N);
memcpy(out, outbuf, SPX_N);
}
#if SPX_SHA512
static void thash_512(unsigned char *out, const unsigned char *in, unsigned int inblocks,
const spx_ctx *ctx, uint32_t addr[8])
{
unsigned char outbuf[SPX_SHA512_OUTPUT_BYTES];
uint8_t sha2_state[72];
SPX_VLA(uint8_t, buf, SPX_SHA256_ADDR_BYTES + inblocks*SPX_N);
/* Retrieve precomputed state containing pub_seed */
memcpy(sha2_state, ctx->state_seeded_512, 72 * sizeof(uint8_t));
memcpy(buf, addr, SPX_SHA256_ADDR_BYTES);
memcpy(buf + SPX_SHA256_ADDR_BYTES, in, inblocks * SPX_N);
sha512_inc_finalize(outbuf, sha2_state, buf, SPX_SHA256_ADDR_BYTES + inblocks*SPX_N);
memcpy(out, outbuf, SPX_N);
}
#endif

View file

@ -0,0 +1,154 @@
#include <string.h>
#include "utils.h"
#include "params.h"
#include "hash.h"
#include "thash.h"
#include "address.h"
/**
* Converts the value of 'in' to 'outlen' bytes in big-endian byte order.
*/
void ull_to_bytes(unsigned char *out, unsigned int outlen,
unsigned long long in)
{
int i;
/* Iterate over out in decreasing order, for big-endianness. */
for (i = (signed int)outlen - 1; i >= 0; i--) {
out[i] = in & 0xff;
in = in >> 8;
}
}
void u32_to_bytes(unsigned char *out, uint32_t in)
{
out[0] = (unsigned char)(in >> 24);
out[1] = (unsigned char)(in >> 16);
out[2] = (unsigned char)(in >> 8);
out[3] = (unsigned char)in;
}
/**
* Converts the inlen bytes in 'in' from big-endian byte order to an integer.
*/
unsigned long long bytes_to_ull(const unsigned char *in, unsigned int inlen)
{
unsigned long long retval = 0;
unsigned int i;
for (i = 0; i < inlen; i++) {
retval |= ((unsigned long long)in[i]) << (8*(inlen - 1 - i));
}
return retval;
}
/**
* Computes a root node given a leaf and an auth path.
* Expects address to be complete other than the tree_height and tree_index.
*/
void compute_root(unsigned char *root, const unsigned char *leaf,
uint32_t leaf_idx, uint32_t idx_offset,
const unsigned char *auth_path, uint32_t tree_height,
const spx_ctx *ctx, uint32_t addr[8])
{
uint32_t i;
unsigned char buffer[2 * SPX_N];
/* If leaf_idx is odd (last bit = 1), current path element is a right child
and auth_path has to go left. Otherwise it is the other way around. */
if (leaf_idx & 1) {
memcpy(buffer + SPX_N, leaf, SPX_N);
memcpy(buffer, auth_path, SPX_N);
}
else {
memcpy(buffer, leaf, SPX_N);
memcpy(buffer + SPX_N, auth_path, SPX_N);
}
auth_path += SPX_N;
for (i = 0; i < tree_height - 1; i++) {
leaf_idx >>= 1;
idx_offset >>= 1;
/* Set the address of the node we're creating. */
set_tree_height(addr, i + 1);
set_tree_index(addr, leaf_idx + idx_offset);
/* Pick the right or left neighbor, depending on parity of the node. */
if (leaf_idx & 1) {
thash(buffer + SPX_N, buffer, 2, ctx, addr);
memcpy(buffer, auth_path, SPX_N);
}
else {
thash(buffer, buffer, 2, ctx, addr);
memcpy(buffer + SPX_N, auth_path, SPX_N);
}
auth_path += SPX_N;
}
/* The last iteration is exceptional; we do not copy an auth_path node. */
leaf_idx >>= 1;
idx_offset >>= 1;
set_tree_height(addr, tree_height);
set_tree_index(addr, leaf_idx + idx_offset);
thash(root, buffer, 2, ctx, addr);
}
/**
* For a given leaf index, computes the authentication path and the resulting
* root node using Merkle's TreeHash algorithm.
* Expects the layer and tree parts of the tree_addr to be set, as well as the
* tree type (i.e. SPX_ADDR_TYPE_HASHTREE or SPX_ADDR_TYPE_FORSTREE).
* Applies the offset idx_offset to indices before building addresses, so that
* it is possible to continue counting indices across trees.
*/
void treehash(unsigned char *root, unsigned char *auth_path, const spx_ctx* ctx,
uint32_t leaf_idx, uint32_t idx_offset, uint32_t tree_height,
void (*gen_leaf)(
unsigned char* /* leaf */,
const spx_ctx* /* ctx */,
uint32_t /* addr_idx */, const uint32_t[8] /* tree_addr */),
uint32_t tree_addr[8])
{
SPX_VLA(uint8_t, stack, (tree_height+1)*SPX_N);
SPX_VLA(unsigned int, heights, tree_height+1);
unsigned int offset = 0;
uint32_t idx;
uint32_t tree_idx;
for (idx = 0; idx < (uint32_t)(1 << tree_height); idx++) {
/* Add the next leaf node to the stack. */
gen_leaf(stack + offset*SPX_N, ctx, idx + idx_offset, tree_addr);
offset++;
heights[offset - 1] = 0;
/* If this is a node we need for the auth path.. */
if ((leaf_idx ^ 0x1) == idx) {
memcpy(auth_path, stack + (offset - 1)*SPX_N, SPX_N);
}
/* While the top-most nodes are of equal height.. */
while (offset >= 2 && heights[offset - 1] == heights[offset - 2]) {
/* Compute index of the new node, in the next layer. */
tree_idx = (idx >> (heights[offset - 1] + 1));
/* Set the address of the node we're creating. */
set_tree_height(tree_addr, heights[offset - 1] + 1);
set_tree_index(tree_addr,
tree_idx + (idx_offset >> (heights[offset-1] + 1)));
/* Hash the top-most nodes from the stack together. */
thash(stack + (offset - 2)*SPX_N,
stack + (offset - 2)*SPX_N, 2, ctx, tree_addr);
offset--;
/* Note that the top-most node is now one layer higher. */
heights[offset - 1]++;
/* If this is a node we need for the auth path.. */
if (((leaf_idx >> heights[offset - 1]) ^ 0x1) == tree_idx) {
memcpy(auth_path + heights[offset - 1]*SPX_N,
stack + (offset - 1)*SPX_N, SPX_N);
}
}
}
memcpy(root, stack, SPX_N);
}

View file

@ -0,0 +1,64 @@
#ifndef SPX_UTILS_H
#define SPX_UTILS_H
#include <stdint.h>
#include "params.h"
#include "context.h"
/* To support MSVC use alloca() instead of VLAs. See #20. */
#ifdef _MSC_VER
/* MSVC defines _alloca in malloc.h */
# include <malloc.h>
/* Note: _malloca(), which is recommended over deprecated _alloca,
requires that you call _freea(). So we stick with _alloca */
# define SPX_VLA(__t,__x,__s) __t *__x = (__t*)_alloca((__s)*sizeof(__t))
#else
# define SPX_VLA(__t,__x,__s) __t __x[__s]
#endif
/**
* Converts the value of 'in' to 'outlen' bytes in big-endian byte order.
*/
#define ull_to_bytes SPX_NAMESPACE(ull_to_bytes)
void ull_to_bytes(unsigned char *out, unsigned int outlen,
unsigned long long in);
#define u32_to_bytes SPX_NAMESPACE(u32_to_bytes)
void u32_to_bytes(unsigned char *out, uint32_t in);
/**
* Converts the inlen bytes in 'in' from big-endian byte order to an integer.
*/
#define bytes_to_ull SPX_NAMESPACE(bytes_to_ull)
unsigned long long bytes_to_ull(const unsigned char *in, unsigned int inlen);
/**
* Computes a root node given a leaf and an auth path.
* Expects address to be complete other than the tree_height and tree_index.
*/
#define compute_root SPX_NAMESPACE(compute_root)
void compute_root(unsigned char *root, const unsigned char *leaf,
uint32_t leaf_idx, uint32_t idx_offset,
const unsigned char *auth_path, uint32_t tree_height,
const spx_ctx *ctx, uint32_t addr[8]);
/**
* For a given leaf index, computes the authentication path and the resulting
* root node using Merkle's TreeHash algorithm.
* Expects the layer and tree parts of the tree_addr to be set, as well as the
* tree type (i.e. SPX_ADDR_TYPE_HASHTREE or SPX_ADDR_TYPE_FORSTREE).
* Applies the offset idx_offset to indices before building addresses, so that
* it is possible to continue counting indices across trees.
*/
#define treehash SPX_NAMESPACE(treehash)
void treehash(unsigned char *root, unsigned char *auth_path,
const spx_ctx* ctx,
uint32_t leaf_idx, uint32_t idx_offset, uint32_t tree_height,
void (*gen_leaf)(
unsigned char* /* leaf */,
const spx_ctx* ctx /* ctx */,
uint32_t /* addr_idx */, const uint32_t[8] /* tree_addr */),
uint32_t tree_addr[8]);
#endif

View file

@ -0,0 +1,100 @@
#include <string.h>
#include "utils.h"
#include "utilsx1.h"
#include "params.h"
#include "thash.h"
#include "address.h"
/*
* Generate the entire Merkle tree, computing the authentication path for
* leaf_idx, and the resulting root node using Merkle's TreeHash algorithm.
* Expects the layer and tree parts of the tree_addr to be set, as well as the
* tree type (i.e. SPX_ADDR_TYPE_HASHTREE or SPX_ADDR_TYPE_FORSTREE)
*
* This expects tree_addr to be initialized to the addr structures for the
* Merkle tree nodes
*
* Applies the offset idx_offset to indices before building addresses, so that
* it is possible to continue counting indices across trees.
*
* This works by using the standard Merkle tree building algorithm,
*/
void treehashx1(unsigned char *root, unsigned char *auth_path,
const spx_ctx* ctx,
uint32_t leaf_idx, uint32_t idx_offset,
uint32_t tree_height,
void (*gen_leaf)(
unsigned char* /* Where to write the leaves */,
const spx_ctx* /* ctx */,
uint32_t idx, void *info),
uint32_t tree_addr[8],
void *info)
{
/* This is where we keep the intermediate nodes */
SPX_VLA(uint8_t, stack, tree_height*SPX_N);
uint32_t idx;
uint32_t max_idx = (uint32_t)((1 << tree_height) - 1);
for (idx = 0;; idx++) {
unsigned char current[2*SPX_N]; /* Current logical node is at */
/* index[SPX_N]. We do this to minimize the number of copies */
/* needed during a thash */
gen_leaf( &current[SPX_N], ctx, idx + idx_offset,
info );
/* Now combine the freshly generated right node with previously */
/* generated left ones */
uint32_t internal_idx_offset = idx_offset;
uint32_t internal_idx = idx;
uint32_t internal_leaf = leaf_idx;
uint32_t h; /* The height we are in the Merkle tree */
for (h=0;; h++, internal_idx >>= 1, internal_leaf >>= 1) {
/* Check if we hit the top of the tree */
if (h == tree_height) {
/* We hit the root; return it */
memcpy( root, &current[SPX_N], SPX_N );
return;
}
/*
* Check if the node we have is a part of the
* authentication path; if it is, write it out
*/
if ((internal_idx ^ internal_leaf) == 0x01) {
memcpy( &auth_path[ h * SPX_N ],
&current[SPX_N],
SPX_N );
}
/*
* Check if we're at a left child; if so, stop going up the stack
* Exception: if we've reached the end of the tree, keep on going
* (so we combine the last 4 nodes into the one root node in two
* more iterations)
*/
if ((internal_idx & 1) == 0 && idx < max_idx) {
break;
}
/* Ok, we're at a right node */
/* Now combine the left and right logical nodes together */
/* Set the address of the node we're creating. */
internal_idx_offset >>= 1;
set_tree_height(tree_addr, h + 1);
set_tree_index(tree_addr, internal_idx/2 + internal_idx_offset );
unsigned char *left = &stack[h * SPX_N];
memcpy( &current[0], left, SPX_N );
thash( &current[1 * SPX_N],
&current[0 * SPX_N],
2, ctx, tree_addr);
}
/* We've hit a left child; save the current for when we get the */
/* corresponding right right */
memcpy( &stack[h * SPX_N], &current[SPX_N], SPX_N);
}
}

View file

@ -0,0 +1,26 @@
#ifndef SPX_UTILSX4_H
#define SPX_UTILSX4_H
#include <stdint.h>
#include "params.h"
#include "context.h"
/**
* For a given leaf index, computes the authentication path and the resulting
* root node using Merkle's TreeHash algorithm.
* Expects the layer and tree parts of the tree_addr to be set, as well as the
* tree type (i.e. SPX_ADDR_TYPE_HASHTREE or SPX_ADDR_TYPE_FORSTREE).
* Applies the offset idx_offset to indices before building addresses, so that
* it is possible to continue counting indices across trees.
*/
#define treehashx1 SPX_NAMESPACE(treehashx1)
void treehashx1(unsigned char *root, unsigned char *auth_path,
const spx_ctx* ctx,
uint32_t leaf_idx, uint32_t idx_offset, uint32_t tree_height,
void (*gen_leaf)(
unsigned char* /* Where to write the leaf */,
const spx_ctx* /* ctx */,
uint32_t addr_idx, void *info),
uint32_t tree_addrx4[8], void *info);
#endif

View file

@ -0,0 +1,112 @@
#include <stdint.h>
#include <string.h>
#include "utils.h"
#include "utilsx1.h"
#include "hash.h"
#include "thash.h"
#include "wots.h"
#include "wotsx1.h"
#include "address.h"
#include "params.h"
// TODO clarify address expectations, and make them more uniform.
// TODO i.e. do we expect types to be set already?
// TODO and do we expect modifications or copies?
/**
* Computes the chaining function.
* out and in have to be n-byte arrays.
*
* Interprets in as start-th value of the chain.
* addr has to contain the address of the chain.
*/
static void gen_chain(unsigned char *out, const unsigned char *in,
unsigned int start, unsigned int steps,
const spx_ctx *ctx, uint32_t addr[8])
{
uint32_t i;
/* Initialize out with the value at position 'start'. */
memcpy(out, in, SPX_N);
/* Iterate 'steps' calls to the hash function. */
for (i = start; i < (start+steps) && i < SPX_WOTS_W; i++) {
set_hash_addr(addr, i);
thash(out, out, 1, ctx, addr);
}
}
/**
* base_w algorithm as described in draft.
* Interprets an array of bytes as integers in base w.
* This only works when log_w is a divisor of 8.
*/
static void base_w(unsigned int *output, const int out_len,
const unsigned char *input)
{
int in = 0;
int out = 0;
unsigned char total;
int bits = 0;
int consumed;
for (consumed = 0; consumed < out_len; consumed++) {
if (bits == 0) {
total = input[in];
in++;
bits += 8;
}
bits -= SPX_WOTS_LOGW;
output[out] = (total >> bits) & (SPX_WOTS_W - 1);
out++;
}
}
/* Computes the WOTS+ checksum over a message (in base_w). */
static void wots_checksum(unsigned int *csum_base_w,
const unsigned int *msg_base_w)
{
unsigned int csum = 0;
unsigned char csum_bytes[(SPX_WOTS_LEN2 * SPX_WOTS_LOGW + 7) / 8];
unsigned int i;
/* Compute checksum. */
for (i = 0; i < SPX_WOTS_LEN1; i++) {
csum += SPX_WOTS_W - 1 - msg_base_w[i];
}
/* Convert checksum to base_w. */
/* Make sure expected empty zero bits are the least significant bits. */
csum = csum << ((8 - ((SPX_WOTS_LEN2 * SPX_WOTS_LOGW) % 8)) % 8);
ull_to_bytes(csum_bytes, sizeof(csum_bytes), csum);
base_w(csum_base_w, SPX_WOTS_LEN2, csum_bytes);
}
/* Takes a message and derives the matching chain lengths. */
void chain_lengths(unsigned int *lengths, const unsigned char *msg)
{
base_w(lengths, SPX_WOTS_LEN1, msg);
wots_checksum(lengths + SPX_WOTS_LEN1, lengths);
}
/**
* Takes a WOTS signature and an n-byte message, computes a WOTS public key.
*
* Writes the computed public key to 'pk'.
*/
void wots_pk_from_sig(unsigned char *pk,
const unsigned char *sig, const unsigned char *msg,
const spx_ctx *ctx, uint32_t addr[8])
{
unsigned int lengths[SPX_WOTS_LEN];
uint32_t i;
chain_lengths(lengths, msg);
for (i = 0; i < SPX_WOTS_LEN; i++) {
set_chain_addr(addr, i);
gen_chain(pk + i*SPX_N, sig + i*SPX_N,
lengths[i], SPX_WOTS_W - 1 - lengths[i], ctx, addr);
}
}

View file

@ -0,0 +1,25 @@
#ifndef SPX_WOTS_H
#define SPX_WOTS_H
#include <stdint.h>
#include "params.h"
#include "context.h"
/**
* Takes a WOTS signature and an n-byte message, computes a WOTS public key.
*
* Writes the computed public key to 'pk'.
*/
#define wots_pk_from_sig SPX_NAMESPACE(wots_pk_from_sig)
void wots_pk_from_sig(unsigned char *pk,
const unsigned char *sig, const unsigned char *msg,
const spx_ctx *ctx, uint32_t addr[8]);
/*
* Compute the chain lengths needed for a given message hash
*/
#define chain_lengths SPX_NAMESPACE(chain_lengths)
void chain_lengths(unsigned int *lengths, const unsigned char *msg);
#endif

View file

@ -0,0 +1,73 @@
#include <stdint.h>
#include <string.h>
#include "utils.h"
#include "hash.h"
#include "thash.h"
#include "wots.h"
#include "wotsx1.h"
#include "address.h"
#include "params.h"
/*
* This generates a WOTS public key
* It also generates the WOTS signature if leaf_info indicates
* that we're signing with this WOTS key
*/
void wots_gen_leafx1(unsigned char *dest,
const spx_ctx *ctx,
uint32_t leaf_idx, void *v_info) {
struct leaf_info_x1 *info = v_info;
uint32_t *leaf_addr = info->leaf_addr;
uint32_t *pk_addr = info->pk_addr;
unsigned int i, k;
unsigned char pk_buffer[ SPX_WOTS_BYTES ];
unsigned char *buffer;
uint32_t wots_k_mask;
if (leaf_idx == info->wots_sign_leaf) {
/* We're traversing the leaf that's signing; generate the WOTS */
/* signature */
wots_k_mask = 0;
} else {
/* Nope, we're just generating pk's; turn off the signature logic */
wots_k_mask = (uint32_t)~0;
}
set_keypair_addr( leaf_addr, leaf_idx );
set_keypair_addr( pk_addr, leaf_idx );
for (i = 0, buffer = pk_buffer; i < SPX_WOTS_LEN; i++, buffer += SPX_N) {
uint32_t wots_k = info->wots_steps[i] | wots_k_mask; /* Set wots_k to */
/* the step if we're generating a signature, ~0 if we're not */
/* Start with the secret seed */
set_chain_addr(leaf_addr, i);
set_hash_addr(leaf_addr, 0);
set_type(leaf_addr, SPX_ADDR_TYPE_WOTSPRF);
prf_addr(buffer, ctx, leaf_addr);
set_type(leaf_addr, SPX_ADDR_TYPE_WOTS);
/* Iterate down the WOTS chain */
for (k=0;; k++) {
/* Check if this is the value that needs to be saved as a */
/* part of the WOTS signature */
if (k == wots_k) {
memcpy( info->wots_sig + i * SPX_N, buffer, SPX_N );
}
/* Check if we hit the top of the chain */
if (k == SPX_WOTS_W - 1) break;
/* Iterate one step on the chain */
set_hash_addr(leaf_addr, k);
thash(buffer, buffer, 1, ctx, leaf_addr);
}
}
/* Do the final thash to generate the public keys */
thash(dest, pk_buffer, SPX_WOTS_LEN, ctx, pk_addr);
}

View file

@ -0,0 +1,36 @@
#if !defined( WOTSX1_H_ )
#define WOTSX1_H_
#include <string.h>
/*
* This is here to provide an interface to the internal wots_gen_leafx1
* routine. While this routine is not referenced in the package outside of
* wots.c, it is called from the stand-alone benchmark code to characterize
* the performance
*/
struct leaf_info_x1 {
unsigned char *wots_sig;
uint32_t wots_sign_leaf; /* The index of the WOTS we're using to sign */
uint32_t *wots_steps;
uint32_t leaf_addr[8];
uint32_t pk_addr[8];
};
/* Macro to set the leaf_info to something 'benign', that is, it would */
/* run with the same time as it does during the real signing process */
/* Used only by the benchmark code */
#define INITIALIZE_LEAF_INFO_X1(info, addr, step_buffer) { \
info.wots_sig = 0; \
info.wots_sign_leaf = ~0u; \
info.wots_steps = step_buffer; \
memcpy( &info.leaf_addr[0], addr, 32 ); \
memcpy( &info.pk_addr[0], addr, 32 ); \
}
#define wots_gen_leafx1 SPX_NAMESPACE(wots_gen_leafx1)
void wots_gen_leafx1(unsigned char *dest,
const spx_ctx *ctx,
uint32_t leaf_idx, void *v_info);
#endif /* WOTSX1_H_ */

View file

@ -0,0 +1,345 @@
#include <jni.h>
#include <array>
#include <cstdint>
#include <vector>
#include "pq/pq.h"
namespace {
jbyteArray to_jbyte_array(
JNIEnv* env,
const uint8_t* data,
size_t size)
{
jbyteArray result =
env->NewByteArray(static_cast<jsize>(size));
if (result == nullptr) {
return nullptr;
}
env->SetByteArrayRegion(
result,
0,
static_cast<jsize>(size),
reinterpret_cast<const jbyte*>(data)
);
return result;
}
bool copy_seed(
JNIEnv* env,
jbyteArray seed_array,
std::array<uint8_t, 64>& seed)
{
if (seed_array == nullptr ||
env->GetArrayLength(seed_array) != 64) {
return false;
}
env->GetByteArrayRegion(
seed_array,
0,
64,
reinterpret_cast<jbyte*>(seed.data())
);
return !env->ExceptionCheck();
}
} // namespace
extern "C"
JNIEXPORT jstring JNICALL
Java_org_vextaproject_wallet_VextaPQ_nativeVersion(
JNIEnv* env,
jobject)
{
return env->NewStringUTF("Vexta PQ JNI 0.2");
}
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_org_vextaproject_wallet_VextaPQ_mldsaKeypairFromSeed(
JNIEnv* env,
jobject,
jbyteArray seed_array)
{
std::array<uint8_t, 64> seed{};
if (!copy_seed(env, seed_array, seed)) {
return nullptr;
}
std::vector<uint8_t> public_key(
pq::MLDSA65::PUBKEY_BYTES
);
std::vector<uint8_t> secret_key(
pq::MLDSA65::SECKEY_BYTES
);
if (!pq::MLDSA65::keygen_from_seed(
public_key.data(),
secret_key.data(),
seed.data())) {
return nullptr;
}
jclass byte_array_class = env->FindClass("[B");
jobjectArray result =
env->NewObjectArray(2, byte_array_class, nullptr);
if (result == nullptr) {
return nullptr;
}
env->SetObjectArrayElement(
result,
0,
to_jbyte_array(
env,
public_key.data(),
public_key.size()
)
);
env->SetObjectArrayElement(
result,
1,
to_jbyte_array(
env,
secret_key.data(),
secret_key.size()
)
);
return result;
}
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_org_vextaproject_wallet_VextaPQ_sphincsKeypairFromSeed(
JNIEnv* env,
jobject,
jbyteArray seed_array)
{
std::array<uint8_t, 64> seed{};
if (!copy_seed(env, seed_array, seed)) {
return nullptr;
}
std::vector<uint8_t> public_key(
pq::SPHINCS128s::PUBKEY_BYTES
);
std::vector<uint8_t> secret_key(
pq::SPHINCS128s::SECKEY_BYTES
);
if (!pq::SPHINCS128s::keygen_from_seed(
public_key.data(),
secret_key.data(),
seed.data())) {
return nullptr;
}
jclass byte_array_class = env->FindClass("[B");
jobjectArray result =
env->NewObjectArray(2, byte_array_class, nullptr);
if (result == nullptr) {
return nullptr;
}
env->SetObjectArrayElement(
result,
0,
to_jbyte_array(
env,
public_key.data(),
public_key.size()
)
);
env->SetObjectArrayElement(
result,
1,
to_jbyte_array(
env,
secret_key.data(),
secret_key.size()
)
);
return result;
}
namespace {
bool copy_byte_array(
JNIEnv* env,
jbyteArray input,
std::vector<uint8_t>& output)
{
if (input == nullptr) {
return false;
}
const jsize length = env->GetArrayLength(input);
output.resize(static_cast<size_t>(length));
if (length > 0) {
env->GetByteArrayRegion(
input,
0,
length,
reinterpret_cast<jbyte*>(output.data())
);
}
return !env->ExceptionCheck();
}
} // namespace
extern "C"
JNIEXPORT jbyteArray JNICALL
Java_org_vextaproject_wallet_VextaPQ_mldsaSign(
JNIEnv* env,
jobject,
jbyteArray message_array,
jbyteArray secret_key_array)
{
std::vector<uint8_t> message;
std::vector<uint8_t> secret_key;
if (!copy_byte_array(env, message_array, message) ||
!copy_byte_array(env, secret_key_array, secret_key) ||
secret_key.size() != pq::MLDSA65::SECKEY_BYTES) {
return nullptr;
}
std::vector<uint8_t> signature(
pq::MLDSA65::SIG_BYTES
);
size_t signature_length = signature.size();
if (!pq::MLDSA65::sign(
signature.data(),
&signature_length,
message.data(),
message.size(),
secret_key.data())) {
return nullptr;
}
signature.resize(signature_length);
return to_jbyte_array(
env,
signature.data(),
signature.size()
);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_org_vextaproject_wallet_VextaPQ_mldsaVerify(
JNIEnv* env,
jobject,
jbyteArray signature_array,
jbyteArray message_array,
jbyteArray public_key_array)
{
std::vector<uint8_t> signature;
std::vector<uint8_t> message;
std::vector<uint8_t> public_key;
if (!copy_byte_array(env, signature_array, signature) ||
!copy_byte_array(env, message_array, message) ||
!copy_byte_array(env, public_key_array, public_key) ||
public_key.size() != pq::MLDSA65::PUBKEY_BYTES) {
return JNI_FALSE;
}
return pq::MLDSA65::verify(
signature.data(),
signature.size(),
message.data(),
message.size(),
public_key.data()
) ? JNI_TRUE : JNI_FALSE;
}
extern "C"
JNIEXPORT jbyteArray JNICALL
Java_org_vextaproject_wallet_VextaPQ_sphincsSign(
JNIEnv* env,
jobject,
jbyteArray message_array,
jbyteArray secret_key_array)
{
std::vector<uint8_t> message;
std::vector<uint8_t> secret_key;
if (!copy_byte_array(env, message_array, message) ||
!copy_byte_array(env, secret_key_array, secret_key) ||
secret_key.size() != pq::SPHINCS128s::SECKEY_BYTES) {
return nullptr;
}
std::vector<uint8_t> signature(
pq::SPHINCS128s::SIG_BYTES
);
size_t signature_length = signature.size();
if (!pq::SPHINCS128s::sign(
signature.data(),
&signature_length,
message.data(),
message.size(),
secret_key.data())) {
return nullptr;
}
signature.resize(signature_length);
return to_jbyte_array(
env,
signature.data(),
signature.size()
);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_org_vextaproject_wallet_VextaPQ_sphincsVerify(
JNIEnv* env,
jobject,
jbyteArray signature_array,
jbyteArray message_array,
jbyteArray public_key_array)
{
std::vector<uint8_t> signature;
std::vector<uint8_t> message;
std::vector<uint8_t> public_key;
if (!copy_byte_array(env, signature_array, signature) ||
!copy_byte_array(env, message_array, message) ||
!copy_byte_array(env, public_key_array, public_key) ||
public_key.size() != pq::SPHINCS128s::PUBKEY_BYTES) {
return JNI_FALSE;
}
return pq::SPHINCS128s::verify(
signature.data(),
signature.size(),
message.data(),
message.size(),
public_key.data()
) ? JNI_TRUE : JNI_FALSE;
}

View file

@ -0,0 +1,93 @@
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <fcntl.h>
#include <sys/syscall.h>
#include <unistd.h>
namespace {
bool fill_from_getrandom(uint8_t* out, size_t outlen)
{
#ifdef SYS_getrandom
while (outlen > 0) {
const long result = syscall(
SYS_getrandom,
out,
outlen,
0
);
if (result > 0) {
out += static_cast<size_t>(result);
outlen -= static_cast<size_t>(result);
continue;
}
if (result < 0 && errno == EINTR) {
continue;
}
return false;
}
return true;
#else
(void)out;
(void)outlen;
return false;
#endif
}
bool fill_from_urandom(uint8_t* out, size_t outlen)
{
const int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return false;
}
while (outlen > 0) {
const ssize_t result = read(fd, out, outlen);
if (result > 0) {
out += static_cast<size_t>(result);
outlen -= static_cast<size_t>(result);
continue;
}
if (result < 0 && errno == EINTR) {
continue;
}
close(fd);
return false;
}
close(fd);
return true;
}
} // namespace
extern "C" void randombytes(uint8_t* out, size_t outlen)
{
if (outlen == 0) {
return;
}
if (out == nullptr) {
std::abort();
}
if (fill_from_getrandom(out, outlen)) {
return;
}
if (fill_from_urandom(out, outlen)) {
return;
}
std::abort();
}

View file

@ -23,13 +23,20 @@ object BlockScanner {
private const val genesisHash =
"0000027626959d894ae1c8a6f9050cdcbdf58eb0037ab06345cb5502955370f1"
enum class AddressType {
STANDARD,
MLDSA,
SLHDSA
}
data class SpendableUtxo(
val txid: String,
val outputIndex: Long,
val value: Long,
val height: Int,
val addressIndex: Int,
val isCoinbase: Boolean
val isCoinbase: Boolean,
val addressType: AddressType = AddressType.STANDARD
)
data class WalletTransaction(
@ -59,7 +66,8 @@ object BlockScanner {
val value: Long,
val height: Int,
val addressIndex: Int,
val isCoinbase: Boolean
val isCoinbase: Boolean,
val addressType: AddressType
)
private data class ParsedTransaction(
@ -86,13 +94,17 @@ object BlockScanner {
matchingHeights: List<Int>,
walletScript: ByteArray,
addressIndex: Int,
addressType: AddressType = AddressType.STANDARD,
initialUtxos: List<SpendableUtxo> = emptyList(),
progress: (Int, Int) -> Unit
): Result {
if (matchingHeights.isEmpty()) {
val existing =
initialUtxos
.filter { it.addressIndex == addressIndex }
.filter {
it.addressIndex == addressIndex &&
it.addressType == addressType
}
.sortedWith(
compareBy<SpendableUtxo> { it.height }
.thenBy { it.txid }
@ -128,7 +140,10 @@ object BlockScanner {
val utxos = linkedMapOf<String, Utxo>()
initialUtxos
.filter { it.addressIndex == addressIndex }
.filter {
it.addressIndex == addressIndex &&
it.addressType == addressType
}
.forEach { existing ->
val txidWire =
existing.txid
@ -144,7 +159,8 @@ object BlockScanner {
value = existing.value,
height = existing.height,
addressIndex = existing.addressIndex,
isCoinbase = existing.isCoinbase
isCoinbase = existing.isCoinbase,
addressType = existing.addressType
)
}
var receivedTransactions = 0
@ -241,7 +257,8 @@ object BlockScanner {
value = transactionOutput.value,
height = height,
addressIndex = addressIndex,
isCoinbase = transaction.isCoinbase
isCoinbase = transaction.isCoinbase,
addressType = addressType
)
receivedWalletValue +=
@ -290,7 +307,8 @@ object BlockScanner {
value = utxo.value,
height = utxo.height,
addressIndex = utxo.addressIndex,
isCoinbase = utxo.isCoinbase
isCoinbase = utxo.isCoinbase,
addressType = utxo.addressType
)
}

View file

@ -0,0 +1,39 @@
package org.vextaproject.wallet
object VextaPQ {
init {
System.loadLibrary("vextapq")
}
external fun nativeVersion(): String
external fun mldsaKeypairFromSeed(
seed: ByteArray
): Array<ByteArray>?
external fun sphincsKeypairFromSeed(
seed: ByteArray
): Array<ByteArray>?
external fun mldsaSign(
message: ByteArray,
secretKey: ByteArray
): ByteArray?
external fun mldsaVerify(
signature: ByteArray,
message: ByteArray,
publicKey: ByteArray
): Boolean
external fun sphincsSign(
message: ByteArray,
secretKey: ByteArray
): ByteArray?
external fun sphincsVerify(
signature: ByteArray,
message: ByteArray,
publicKey: ByteArray
): Boolean
}

View file

@ -48,6 +48,13 @@ object VextaTransactionSender {
val fee: Long
)
data class PqSigningKey(
val addressType: BlockScanner.AddressType,
val publicKey: ByteArray,
val secretKey: ByteArray
)
private const val COINBASE_MATURITY = 100
private fun validateCoinbaseMaturity(
@ -90,9 +97,9 @@ object VextaTransactionSender {
Math.addExact(sum, utxo.value)
}
val fee = estimateFee(
inputCount = spendableUtxos.size,
outputCount = 1
val fee = estimateFeeFromOutputVirtualBytes(
inputs = spendableUtxos,
outputVirtualBytes = listOf(43L)
)
val amount = total - fee
@ -112,6 +119,8 @@ object VextaTransactionSender {
recipientAddress: String,
amountSatoshis: Long,
privateKeysByIndex: Map<Int, ECKey>,
pqSigningKeys:
Map<Pair<BlockScanner.AddressType, Int>, PqSigningKey>,
changePubKeyHash: ByteArray,
chainHeight: Int
): CreatedTransaction {
@ -127,20 +136,33 @@ object VextaTransactionSender {
"Invalid change public-key hash"
}
require(privateKeysByIndex.isNotEmpty()) {
"No wallet private keys supplied"
require(
privateKeysByIndex.isNotEmpty() ||
pqSigningKeys.isNotEmpty()
) {
"No wallet signing keys supplied"
}
val recipientScript = addressToScript(recipientAddress)
val changeScript =
byteArrayOf(0x00, 0x14) + changePubKeyHash
val orderedUtxos = spendableUtxos.sortedWith(
compareBy<BlockScanner.SpendableUtxo> { it.height }
val orderedUtxos =
spendableUtxos
.sortedWith(
compareBy<BlockScanner.SpendableUtxo> {
it.height
}
.thenBy { it.txid }
.thenBy { it.outputIndex }
)
if (orderedUtxos.isEmpty()) {
throw IllegalStateException(
"No spendable outputs available"
)
}
val maxSpend =
calculateMaxSpend(
orderedUtxos,
@ -172,8 +194,11 @@ object VextaTransactionSender {
Math.addExact(selectedValue, utxo.value)
val estimatedFee = estimateFee(
inputCount = selected.size,
outputCount = 2
inputs = selected,
outputScripts = listOf(
recipientScript,
changeScript
)
)
if (
@ -191,8 +216,11 @@ object VextaTransactionSender {
}
fee = estimateFee(
inputCount = selected.size,
outputCount = 2
inputs = selected,
outputScripts = listOf(
recipientScript,
changeScript
)
)
if (selectedValue < amountSatoshis + fee) {
@ -267,6 +295,8 @@ object VextaTransactionSender {
}
selected.forEachIndexed { index, utxo ->
when (utxo.addressType) {
BlockScanner.AddressType.STANDARD -> {
val privateKey =
privateKeysByIndex[utxo.addressIndex]
?: throw IllegalStateException(
@ -292,7 +322,9 @@ object VextaTransactionSender {
false
)
transaction.getInput(index.toLong()).setWitness(
transaction
.getInput(index.toLong())
.setWitness(
TransactionWitness.redeemP2WPKH(
signature,
privateKey
@ -300,6 +332,92 @@ object VextaTransactionSender {
)
}
BlockScanner.AddressType.MLDSA,
BlockScanner.AddressType.SLHDSA -> {
val pqKey =
pqSigningKeys[
utxo.addressType to
utxo.addressIndex
] ?: throw IllegalStateException(
"Missing PQ signing key for " +
"${utxo.addressType} address index " +
utxo.addressIndex
)
require(
pqKey.addressType == utxo.addressType
) {
"PQ signing key type mismatch"
}
val witnessVersion =
when (utxo.addressType) {
BlockScanner.AddressType.MLDSA -> 2
BlockScanner.AddressType.SLHDSA -> 3
BlockScanner.AddressType.STANDARD ->
error("Standard input is not PQ")
}
val witnessProgram =
MessageDigest
.getInstance("SHA-256")
.digest(pqKey.publicKey)
val signatureHash =
calculatePqrSignatureHash(
selected = selected,
outputs = outputs,
inputIndex = index,
witnessVersion = witnessVersion,
witnessProgram = witnessProgram,
amount = utxo.value,
version = 2,
lockTime = 0L,
inputSequence = sequence
)
val signature =
try {
when (utxo.addressType) {
BlockScanner.AddressType.MLDSA ->
requireNotNull(
VextaPQ.mldsaSign(
signatureHash,
pqKey.secretKey
)
) {
"ML-DSA signing failed"
}
BlockScanner.AddressType.SLHDSA ->
requireNotNull(
VextaPQ.sphincsSign(
signatureHash,
pqKey.secretKey
)
) {
"SLH-DSA signing failed"
}
BlockScanner.AddressType.STANDARD ->
error("Standard input is not PQ")
}
} finally {
pqKey.secretKey.fill(0)
}
transaction
.getInput(index.toLong())
.setWitness(
createPqWitness(
signature,
pqKey.publicKey
)
)
}
}
}
val rawTransaction =
transaction.bitcoinSerialize()
@ -340,6 +458,468 @@ object VextaTransactionSender {
return successfulPeers
}
private fun calculatePqrSignatureHash(
selected: List<BlockScanner.SpendableUtxo>,
outputs: List<TransactionOutputData>,
inputIndex: Int,
witnessVersion: Int,
witnessProgram: ByteArray,
amount: Long,
version: Int = 2,
lockTime: Long = 0L,
inputSequence: Long = sequence
): ByteArray {
require(inputIndex in selected.indices) {
"Invalid PQR input index"
}
require(witnessVersion == 2 || witnessVersion == 3) {
"Invalid PQR witness version"
}
require(witnessProgram.size == 32) {
"Invalid PQR witness program"
}
val hashPrevouts = doubleSha256(
ByteArrayOutputStream().apply {
selected.forEach { utxo ->
write(hashDisplayToWire(utxo.txid))
writeUInt32LE(this, utxo.outputIndex)
}
}.toByteArray()
)
val hashSequence = doubleSha256(
ByteArrayOutputStream().apply {
selected.forEach {
writeUInt32LE(this, inputSequence)
}
}.toByteArray()
)
val hashOutputs = doubleSha256(
ByteArrayOutputStream().apply {
outputs.forEach { transactionOutput ->
writeInt64LE(this, transactionOutput.value)
writeCompactSize(
this,
transactionOutput.script.size.toLong()
)
write(transactionOutput.script)
}
}.toByteArray()
)
val currentInput = selected[inputIndex]
val scriptCode =
byteArrayOf(
if (witnessVersion == 2) {
0x52
} else {
0x53
},
0x20
) + witnessProgram
val preimage = ByteArrayOutputStream().apply {
write(0x00)
writeInt32LE(this, version)
write(hashPrevouts)
write(hashSequence)
write(hashDisplayToWire(currentInput.txid))
writeUInt32LE(this, currentInput.outputIndex)
writeCompactSize(this, scriptCode.size.toLong())
write(scriptCode)
writeInt64LE(this, amount)
writeUInt32LE(this, inputSequence)
write(hashOutputs)
writeUInt32LE(this, lockTime)
writeUInt32LE(this, 1L)
}.toByteArray()
return doubleSha256(preimage)
}
fun runPqrSignatureHashSelfTest(): Pair<String, String> {
val selected = listOf(
BlockScanner.SpendableUtxo(
txid =
"0000000000000000000000000000000000000000000000000000000000000001",
outputIndex = 3L,
value = 987654L,
height = 1,
addressIndex = 0,
isCoinbase = false,
addressType = BlockScanner.AddressType.MLDSA
)
)
val outputs = listOf(
TransactionOutputData(
value = 123456L,
script = byteArrayOf(0x51)
)
)
val program =
ByteArray(32) { index ->
index.toByte()
}
val v2 = calculatePqrSignatureHash(
selected = selected,
outputs = outputs,
inputIndex = 0,
witnessVersion = 2,
witnessProgram = program,
amount = 987654L,
version = 2,
lockTime = 500L,
inputSequence = 0xfffffffeL
)
val v3 = calculatePqrSignatureHash(
selected = selected,
outputs = outputs,
inputIndex = 0,
witnessVersion = 3,
witnessProgram = program,
amount = 987654L,
version = 2,
lockTime = 500L,
inputSequence = 0xfffffffeL
)
val v2CoreHex = v2.reversedArray().toHex()
val v3CoreHex = v3.reversedArray().toHex()
check(
v2CoreHex ==
"ba39c447b4577db974473a1e7ed2182686672f2900045a40aee6b4dc5811995b"
) {
"PQR V2 signature hash self-test failed: $v2CoreHex"
}
check(
v3CoreHex ==
"dab3188530f1094ef87087451da77bfba77dfc6e63d1730bb9d0475cebe76618"
) {
"PQR V3 signature hash self-test failed: $v3CoreHex"
}
return v2CoreHex to v3CoreHex
}
private fun createPqWitness(
signature: ByteArray,
publicKey: ByteArray
): TransactionWitness {
require(signature.isNotEmpty()) {
"PQ signature is empty"
}
require(publicKey.isNotEmpty()) {
"PQ public key is empty"
}
return TransactionWitness(2).apply {
setPush(0, signature)
setPush(1, publicKey)
}
}
fun runPqWitnessSelfTest(): List<String> {
val seed =
ByteArray(64) { index ->
index.toByte()
}
val mldsa =
requireNotNull(
VextaPQ.mldsaKeypairFromSeed(seed)
)
val sphincs =
requireNotNull(
VextaPQ.sphincsKeypairFromSeed(seed)
)
val message =
"Vexta PQ witness self-test"
.toByteArray(Charsets.UTF_8)
val mldsaSignature =
requireNotNull(
VextaPQ.mldsaSign(
message,
mldsa[1]
)
)
val sphincsSignature =
requireNotNull(
VextaPQ.sphincsSign(
message,
sphincs[1]
)
)
val mldsaWitness =
createPqWitness(
mldsaSignature,
mldsa[0]
)
val sphincsWitness =
createPqWitness(
sphincsSignature,
sphincs[0]
)
check(mldsaWitness.pushCount == 2)
check(mldsaWitness.getPush(0).size == 3309)
check(mldsaWitness.getPush(1).size == 1952)
check(sphincsWitness.pushCount == 2)
check(sphincsWitness.getPush(0).size == 7856)
check(sphincsWitness.getPush(1).size == 32)
mldsa[1].fill(0)
sphincs[1].fill(0)
return listOf(
"MLDSA_WITNESS_PUSHES=${mldsaWitness.pushCount}",
"MLDSA_SIG_BYTES=${mldsaWitness.getPush(0).size}",
"MLDSA_PUBKEY_BYTES=${mldsaWitness.getPush(1).size}",
"SLHDSA_WITNESS_PUSHES=${sphincsWitness.pushCount}",
"SLHDSA_SIG_BYTES=${sphincsWitness.getPush(0).size}",
"SLHDSA_PUBKEY_BYTES=${sphincsWitness.getPush(1).size}",
"PQ_WITNESS_RESULT=PASS"
)
}
fun runPqSignedTransactionSelfTest(): List<String> {
val recipientAddress =
"vtx1z6engqms3emse5lycna6yt7gd6svu7nfd28dccr7mfs8s54pz8rysd6lu5u"
val recipientScript =
addressToScript(recipientAddress)
val changePubKeyHash =
ByteArray(20) { index ->
(0x40 + index).toByte()
}
val changeScript =
byteArrayOf(0x00, 0x14) + changePubKeyHash
val amountSatoshis = 100_000L
val utxoValue = 10_000_000L
fun testType(
addressType: BlockScanner.AddressType,
witnessVersion: Int,
expectedSignatureBytes: Int,
expectedPublicKeyBytes: Int,
txidSuffix: String
): List<String> {
val seed =
ByteArray(64) { index ->
index.toByte()
}
val keyPair =
when (addressType) {
BlockScanner.AddressType.MLDSA ->
requireNotNull(
VextaPQ.mldsaKeypairFromSeed(seed)
)
BlockScanner.AddressType.SLHDSA ->
requireNotNull(
VextaPQ.sphincsKeypairFromSeed(seed)
)
BlockScanner.AddressType.STANDARD ->
error("Standard is not a PQ self-test type")
}
require(keyPair.size == 2)
val publicKey = keyPair[0]
val secretKey = keyPair[1]
val syntheticTxid =
txidSuffix.padStart(64, '0')
val utxo =
BlockScanner.SpendableUtxo(
txid = syntheticTxid,
outputIndex = 0L,
value = utxoValue,
height = 1,
addressIndex = 0,
isCoinbase = false,
addressType = addressType
)
val pqSigningKeys =
mapOf(
(addressType to 0) to
PqSigningKey(
addressType = addressType,
publicKey = publicKey,
secretKey = secretKey
)
)
val created =
createAndSign(
spendableUtxos = listOf(utxo),
recipientAddress = recipientAddress,
amountSatoshis = amountSatoshis,
privateKeysByIndex = emptyMap(),
pqSigningKeys = pqSigningKeys,
changePubKeyHash = changePubKeyHash,
chainHeight = 10_000
)
val parsed =
Transaction(
MainNetParams.get(),
created.rawTransaction
)
check(parsed.inputs.size == 1)
val witness =
parsed.getInput(0L).getWitness()
check(witness.pushCount == 2)
val signature =
witness.getPush(0)
val parsedPublicKey =
witness.getPush(1)
check(signature.size == expectedSignatureBytes)
check(parsedPublicKey.size == expectedPublicKeyBytes)
check(parsedPublicKey.contentEquals(publicKey))
check(
parsed.txId.toString() ==
created.txid
)
val witnessProgram =
MessageDigest
.getInstance("SHA-256")
.digest(publicKey)
val outputs =
mutableListOf(
TransactionOutputData(
value = created.amount,
script = recipientScript
)
)
if (created.change > 0L) {
outputs.add(
TransactionOutputData(
value = created.change,
script = changeScript
)
)
}
val signatureHash =
calculatePqrSignatureHash(
selected = listOf(utxo),
outputs = outputs,
inputIndex = 0,
witnessVersion = witnessVersion,
witnessProgram = witnessProgram,
amount = utxo.value,
version = 2,
lockTime = 0L,
inputSequence = sequence
)
val verified =
when (addressType) {
BlockScanner.AddressType.MLDSA ->
VextaPQ.mldsaVerify(
signature,
signatureHash,
publicKey
)
BlockScanner.AddressType.SLHDSA ->
VextaPQ.sphincsVerify(
signature,
signatureHash,
publicKey
)
BlockScanner.AddressType.STANDARD ->
false
}
check(verified)
seed.fill(0)
signatureHash.fill(0)
return listOf(
"${addressType.name}_SIGNED_TX_BYTES=" +
created.rawTransaction.size,
"${addressType.name}_SIGNED_TX_WITNESS_PUSHES=" +
witness.pushCount,
"${addressType.name}_SIGNED_TX_SIG_BYTES=" +
signature.size,
"${addressType.name}_SIGNED_TX_PUBKEY_BYTES=" +
parsedPublicKey.size,
"${addressType.name}_SIGNED_TX_VERIFY=$verified",
"${addressType.name}_SIGNED_TX_TXID_MATCH=true"
)
}
val results =
mutableListOf<String>()
results +=
testType(
addressType = BlockScanner.AddressType.MLDSA,
witnessVersion = 2,
expectedSignatureBytes = 3309,
expectedPublicKeyBytes = 1952,
txidSuffix = "01"
)
results +=
testType(
addressType = BlockScanner.AddressType.SLHDSA,
witnessVersion = 3,
expectedSignatureBytes = 7856,
expectedPublicKeyBytes = 32,
txidSuffix = "02"
)
results += "PQ_SIGNED_TX_RESULT=PASS"
return results
}
private fun serializeTransaction(
selected: List<BlockScanner.SpendableUtxo>,
outputs: List<TransactionOutputData>,
@ -401,14 +981,64 @@ object VextaTransactionSender {
}.toByteArray()
}
private fun estimateFee(
inputCount: Int,
outputCount: Int
private fun estimatedInputVirtualBytes(
addressType: BlockScanner.AddressType
): Long {
return when (addressType) {
BlockScanner.AddressType.STANDARD ->
69L
BlockScanner.AddressType.MLDSA ->
1358L
BlockScanner.AddressType.SLHDSA ->
2015L
}
}
private fun estimatedOutputVirtualBytes(
script: ByteArray
): Long {
val scriptLengthBytes =
when {
script.size < 253 -> 1L
script.size <= 0xffff -> 3L
else -> 5L
}
return 8L +
scriptLengthBytes +
script.size.toLong()
}
private fun estimateFee(
inputs: List<BlockScanner.SpendableUtxo>,
outputScripts: List<ByteArray>
): Long {
return estimateFeeFromOutputVirtualBytes(
inputs = inputs,
outputVirtualBytes =
outputScripts.map {
estimatedOutputVirtualBytes(it)
}
)
}
private fun estimateFeeFromOutputVirtualBytes(
inputs: List<BlockScanner.SpendableUtxo>,
outputVirtualBytes: List<Long>
): Long {
val inputVirtualBytes =
inputs.sumOf {
estimatedInputVirtualBytes(
it.addressType
)
}
val virtualBytes =
11L +
inputCount * 69L +
outputCount * 31L
inputVirtualBytes +
outputVirtualBytes.sum()
return (
virtualBytes * defaultFeeSatoshisPerKb + 999L
@ -419,13 +1049,33 @@ object VextaTransactionSender {
val value = address.trim()
if (value.lowercase().startsWith("vtx1")) {
val witnessProgram = decodeBech32Witness(value)
val (witnessVersion, witnessProgram) =
decodeBech32Witness(value)
return when (witnessVersion) {
0 -> {
require(witnessProgram.size == 20) {
"Only Vexta P2WPKH addresses are supported"
"Only Vexta P2WPKH witness v0 addresses are supported"
}
byteArrayOf(0x00, 0x14) + witnessProgram
}
return byteArrayOf(0x00, 0x14) + witnessProgram
2 -> {
require(witnessProgram.size == 32) {
"Invalid ML-DSA witness program"
}
byteArrayOf(0x52, 0x20) + witnessProgram
}
3 -> {
require(witnessProgram.size == 32) {
"Invalid SLH-DSA witness program"
}
byteArrayOf(0x53, 0x20) + witnessProgram
}
else -> error("Unsupported witness version")
}
}
val decoded = decodeBase58Check(value)
@ -452,7 +1102,9 @@ object VextaTransactionSender {
)
}
private fun decodeBech32Witness(address: String): ByteArray {
private fun decodeBech32Witness(
address: String
): Pair<Int, ByteArray> {
require(address == address.lowercase()) {
"Mixed-case Bech32 address"
}
@ -477,21 +1129,38 @@ object VextaTransactionSender {
index
}
require(bech32Polymod(expandHrp(hrp) + values) == 1) {
"Invalid Bech32 checksum"
val data = values.dropLast(6)
require(data.isNotEmpty()) {
"Missing witness version"
}
val data = values.dropLast(6)
require(data.isNotEmpty() && data[0] == 0) {
val witnessVersion = data[0]
require(witnessVersion in 0..16) {
"Unsupported witness version"
}
return convertBits(
val expectedChecksum =
if (witnessVersion == 0) 1 else 0x2bc830a3
require(
bech32Polymod(expandHrp(hrp) + values) ==
expectedChecksum
) {
if (witnessVersion == 0) {
"Invalid Bech32 checksum"
} else {
"Invalid Bech32m checksum"
}
}
val witnessProgram = convertBits(
data.drop(1),
5,
8,
false
).map { it.toByte() }.toByteArray()
return witnessVersion to witnessProgram
}
private fun convertBits(

File diff suppressed because it is too large Load diff

View file

@ -37,6 +37,14 @@ class WalletMonitoringService : Service() {
private const val PREF_IV = "seed_iv"
private const val PREF_RECEIVE_ADDRESS_INDEX =
"receive_address_index"
private const val PREF_MLDSA_ADDRESS_INDEX =
"mldsa_address_index"
private const val PREF_SLHDSA_ADDRESS_INDEX =
"slhdsa_address_index"
private const val PREF_MLDSA_ADDRESS_CREATED =
"mldsa_address_created"
private const val PREF_SLHDSA_ADDRESS_CREATED =
"slhdsa_address_created"
private const val PREF_KNOWN_INCOMING_TXIDS =
"known_incoming_txids"
private const val PREF_TX_NOTIFICATIONS_INITIALIZED =
@ -54,6 +62,16 @@ class WalletMonitoringService : Service() {
private const val BACKGROUND_TX_HISTORY_VERSION = 4
}
private fun backgroundUtxoKey(
addressType: BlockScanner.AddressType,
addressIndex: Int
): String {
return PREF_BACKGROUND_UTXO_PREFIX +
addressType.name +
"_" +
addressIndex
}
private val handler = Handler(Looper.getMainLooper())
private var scanRunning = false
@ -136,6 +154,16 @@ class WalletMonitoringService : Service() {
.getInt(PREF_RECEIVE_ADDRESS_INDEX, 0)
.coerceAtLeast(0)
val highestMldsaAddressIndex =
preferences
.getInt(PREF_MLDSA_ADDRESS_INDEX, 0)
.coerceAtLeast(0)
val highestSlhdsaAddressIndex =
preferences
.getInt(PREF_SLHDSA_ADDRESS_INDEX, 0)
.coerceAtLeast(0)
val localHeight = chain.last().height
preferences.edit()
@ -153,14 +181,45 @@ class WalletMonitoringService : Service() {
)
.coerceAtLeast(0)
val backgroundCacheAvailable =
val standardCacheAvailable =
(0..highestAddressIndex).all { addressIndex ->
preferences.contains(
backgroundUtxoKey(
BlockScanner.AddressType.STANDARD,
addressIndex
)
) ||
preferences.contains(
PREF_BACKGROUND_UTXO_PREFIX +
addressIndex
)
}
val mldsaCacheAvailable =
(0..highestMldsaAddressIndex).all { addressIndex ->
preferences.contains(
backgroundUtxoKey(
BlockScanner.AddressType.MLDSA,
addressIndex
)
)
}
val slhdsaCacheAvailable =
(0..highestSlhdsaAddressIndex).all { addressIndex ->
preferences.contains(
backgroundUtxoKey(
BlockScanner.AddressType.SLHDSA,
addressIndex
)
)
}
val backgroundCacheAvailable =
standardCacheAvailable &&
mldsaCacheAvailable &&
slhdsaCacheAvailable
val transactionHistoryCurrent =
preferences.getInt(
PREF_BACKGROUND_TX_HISTORY_VERSION,
@ -192,12 +251,79 @@ class WalletMonitoringService : Service() {
mutableListOf<BlockScanner.WalletTransaction>()
val updatedUtxos =
linkedMapOf<Int, List<BlockScanner.SpendableUtxo>>()
linkedMapOf<
Pair<BlockScanner.AddressType, Int>,
List<BlockScanner.SpendableUtxo>
>()
val scanTargets =
buildList {
for (addressIndex in 0..highestAddressIndex) {
val script =
deriveWitnessScript(words, addressIndex)
add(
Triple(
BlockScanner.AddressType.STANDARD,
addressIndex,
deriveWitnessScript(
words,
addressIndex
)
)
)
}
if (
preferences.getBoolean(
PREF_MLDSA_ADDRESS_CREATED,
false
)
) {
for (
addressIndex in
0..highestMldsaAddressIndex
) {
add(
Triple(
BlockScanner.AddressType.MLDSA,
addressIndex,
derivePqWitnessScript(
words,
BlockScanner.AddressType.MLDSA,
addressIndex
)
)
)
}
}
if (
preferences.getBoolean(
PREF_SLHDSA_ADDRESS_CREATED,
false
)
) {
for (
addressIndex in
0..highestSlhdsaAddressIndex
) {
add(
Triple(
BlockScanner.AddressType.SLHDSA,
addressIndex,
derivePqWitnessScript(
words,
BlockScanner.AddressType.SLHDSA,
addressIndex
)
)
)
}
}
}
for (
(addressType, addressIndex, script)
in scanTargets
) {
val filters =
CompactFilterClient.scan(
this,
@ -212,6 +338,7 @@ class WalletMonitoringService : Service() {
} else {
loadBackgroundUtxos(
preferences,
addressType,
addressIndex
)
}
@ -223,13 +350,15 @@ class WalletMonitoringService : Service() {
filters.matchingHeights,
script,
addressIndex,
addressType = addressType,
initialUtxos = initialUtxos
) { _, _ -> }
allTransactions.addAll(blocks.transactions)
updatedUtxos[addressIndex] =
blocks.utxos
updatedUtxos[
addressType to addressIndex
] = blocks.utxos
}
val mergedTransactions =
@ -297,15 +426,16 @@ class WalletMonitoringService : Service() {
)
for (
addressIndex in 0..highestAddressIndex
(target, utxos) in updatedUtxos
) {
val (addressType, addressIndex) = target
editor.putStringSet(
PREF_BACKGROUND_UTXO_PREFIX +
addressIndex,
encodeBackgroundUtxos(
updatedUtxos[addressIndex]
?: emptyList()
)
backgroundUtxoKey(
addressType,
addressIndex
),
encodeBackgroundUtxos(utxos)
)
}
@ -536,25 +666,47 @@ class WalletMonitoringService : Service() {
utxo.value.toString(),
utxo.height.toString(),
utxo.addressIndex.toString(),
utxo.isCoinbase.toString()
utxo.isCoinbase.toString(),
utxo.addressType.name
).joinToString("|")
}.toSet()
private fun loadBackgroundUtxos(
preferences: android.content.SharedPreferences,
addressType: BlockScanner.AddressType,
addressIndex: Int
): List<BlockScanner.SpendableUtxo> {
val newKey =
backgroundUtxoKey(
addressType,
addressIndex
)
val legacyKey =
PREF_BACKGROUND_UTXO_PREFIX + addressIndex
val values =
if (preferences.contains(newKey)) {
preferences.getStringSet(
PREF_BACKGROUND_UTXO_PREFIX + addressIndex,
newKey,
emptySet()
) ?: emptySet()
} else if (
addressType == BlockScanner.AddressType.STANDARD
) {
preferences.getStringSet(
legacyKey,
emptySet()
) ?: emptySet()
} else {
emptySet()
}
return values.mapNotNull { value ->
try {
val parts = value.split("|")
if (parts.size != 6) {
if (parts.size != 6 && parts.size != 7) {
return@mapNotNull null
}
@ -564,7 +716,13 @@ class WalletMonitoringService : Service() {
value = parts[2].toLong(),
height = parts[3].toInt(),
addressIndex = parts[4].toInt(),
isCoinbase = parts[5].toBooleanStrict()
isCoinbase = parts[5].toBooleanStrict(),
addressType =
if (parts.size == 7) {
BlockScanner.AddressType.valueOf(parts[6])
} else {
BlockScanner.AddressType.STANDARD
}
)
} catch (_: Exception) {
null
@ -619,6 +777,129 @@ class WalletMonitoringService : Service() {
.toString(Charsets.UTF_8)
}
private fun derivePqWitnessScript(
words: List<String>,
addressType: BlockScanner.AddressType,
addressIndex: Int
): ByteArray {
require(
addressType == BlockScanner.AddressType.MLDSA ||
addressType == BlockScanner.AddressType.SLHDSA
)
require(addressIndex >= 0)
MnemonicCode.INSTANCE.check(words)
val bip39Seed =
MnemonicCode.toSeed(words, "")
val masterKey =
HDKeyDerivation.createMasterPrivateKey(
bip39Seed
)
val walletSeed =
masterKey.privKeyBytes
val domain =
"Vexta-PQ-HD-v1"
.toByteArray(Charsets.US_ASCII)
val data =
ByteArray(domain.size + 1 + 4)
System.arraycopy(
domain,
0,
data,
0,
domain.size
)
var offset = domain.size
data[offset++] =
when (addressType) {
BlockScanner.AddressType.MLDSA -> 2
BlockScanner.AddressType.SLHDSA -> 3
BlockScanner.AddressType.STANDARD ->
error("Standard address is not PQ")
}.toByte()
data[offset++] =
((addressIndex ushr 24) and 0xff).toByte()
data[offset++] =
((addressIndex ushr 16) and 0xff).toByte()
data[offset++] =
((addressIndex ushr 8) and 0xff).toByte()
data[offset] =
(addressIndex and 0xff).toByte()
val mac =
javax.crypto.Mac.getInstance(
"HmacSHA512"
)
mac.init(
javax.crypto.spec.SecretKeySpec(
walletSeed,
"HmacSHA512"
)
)
val seedMaterial =
mac.doFinal(data)
val keyPair =
when (addressType) {
BlockScanner.AddressType.MLDSA ->
requireNotNull(
VextaPQ.mldsaKeypairFromSeed(
seedMaterial
)
)
BlockScanner.AddressType.SLHDSA ->
requireNotNull(
VextaPQ.sphincsKeypairFromSeed(
seedMaterial
)
)
BlockScanner.AddressType.STANDARD ->
error("Standard address is not PQ")
}
require(keyPair.size == 2)
val publicKey = keyPair[0]
val secretKey = keyPair[1]
return try {
val keyId =
java.security.MessageDigest
.getInstance("SHA-256")
.digest(publicKey)
byteArrayOf(
if (
addressType ==
BlockScanner.AddressType.MLDSA
) {
0x52.toByte()
} else {
0x53.toByte()
},
0x20
) + keyId
} finally {
secretKey.fill(0)
seedMaterial.fill(0)
walletSeed.fill(0)
bip39Seed.fill(0)
}
}
private fun deriveWitnessScript(
words: List<String>,
addressIndex: Int