diff --git a/base64.c b/base64.c new file mode 100644 index 0000000..5162e8a --- /dev/null +++ b/base64.c @@ -0,0 +1,214 @@ +/* +This is derived from the libb64 project, which has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "base64.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in >= decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + // fall through + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + // fall through + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + // fall through + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + // fall through + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + // fall through + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + + #ifdef BASE64_USE_NEWLINES + if (state_in->stepcount == BASE64_CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + #endif + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + #ifdef BASE64_USE_NEWLINES + *codechar++ = '\n'; + #endif + + return codechar - code_out; +} + +// These "one-shot" functions were not in the original project, I have added them and still need to be sure they work + +// note - output MUST be large enough to accomodate the base64'd input (i.e. must be 1/3 bigger than input_len plus the null terminator - 65 bytes for 48-byte input) +void base64_encode_oneshot(char* input, int input_len, char* output){ + base64_encodestate s; + char* c = output; + + base64_init_encodestate(&s); + c += base64_encode_block(input, input_len, output, &s); + c += base64_encode_blockend(c, &s); + *c = '\0'; +} + +// note - output MUST be large enough to accomodate the decoded input (i.e. must be at least 3/4 the size of the input - 48 bytes for 64-byte input) +void base64_decode_oneshot(char* input, int input_len, char* output){ + base64_decodestate s; + + base64_init_decodestate(&s); + base64_decode_block(input, input_len, output, &s); +} \ No newline at end of file diff --git a/base64.h b/base64.h new file mode 100644 index 0000000..4f66413 --- /dev/null +++ b/base64.h @@ -0,0 +1,52 @@ +/* +This is derived from the libb64 project, which has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_H +#define BASE64_H + +#define BASE64_CHARS_PER_LINE 4 +//#define BASE64_USE_NEWLINES + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +void base64_encode_oneshot(char* input, int input_len, char* output); +void base64_decode_oneshot(char* input, int input_len, char* output); + +#endif diff --git a/blindsig2 b/blindsig2 new file mode 100755 index 0000000..423c61a Binary files /dev/null and b/blindsig2 differ diff --git a/blindsig2.c b/blindsig2.c new file mode 100644 index 0000000..10b11da --- /dev/null +++ b/blindsig2.c @@ -0,0 +1,188 @@ +// This is a (very rough) test of BLST blind signatures based on run.me from BLST's Python example code +// Do not trust this to be secure, also this doesn't do a lot of the sanity checking yet + +#include +#include +#include +#include +#include "blst/blst.h" + +const byte dst[] = "MY-DST"; +double time_taken; +clock_t t; + +byte signer_private_key[32]; +byte signer_public_key[96]; + +void printbytes(byte *toprint, int length){ + for(int i=0;isk_available + 1; } -int keydump(char* targ_name){ +// Fill a buffer dest of size dest_size with exactly (dest_size - 2) characters and a null terminator, returning 1 if the user supplies any other number +int get_fixed_length(char* dest, size_t dest_size){ + dest[dest_size - 2] = '\0'; // Initialize this place, we're about to need to check it even if fgets never gets here + fgets(dest, dest_size, stdin); + if(dest[dest_size - 2] != '\n') return 1; //If there isn't a newline in the second-last character (i.e. the user specified too many) return 1 + + dest[dest_size - 2] = '\0'; // Cut it down to length and get rid of the newline we're now sure is in the right place + + return 0; +} + +int sign_single(char* targ_name, char* req_b64){ + fstoken_request req; + fstoken_target_descriptor targ; + fstoken_signature sig; + + base64_decode_oneshot((char*) req_b64, 64, (char*) req.id_blind); + + //debug_print_bytes("Signature hex: ", req.id_blind, 48); + + if(get_target(targ_name, &targ) != 2){ + printf("Target or private key unavailable. Are you sure you have a private key for this target?\n"); + return 1; + } + + if(fstoken_sign_request(&req, &sig, &targ)){ + printf("Signing failed. Is the token request valid?\n"); + return 1; + }; + + print_base64(sig.signature, 48); + + return 0; +} + +int interactive_request(char* targ_name, byte* token_id, byte* blinding_factor){ + fstoken_request req; + fstoken_request_descriptor reqdesc; + fstoken_target_descriptor target; + fstoken_signature sig; + fstoken_token token; + char req_base64_encoded[65]; + char sig_base64[66]; + int token_length; + + if(!get_target(targ_name, &target)){ + printf("Invalid target %s\n", targ_name); + return 1; + } + + fstoken_gen_request(&req, &reqdesc, &target, token_id, blinding_factor); + + base64_encode_oneshot((char*) req.id_blind, 48, req_base64_encoded); + + printf("A Token Request Code has been generated. Provide this code to the signer. Once you have received a signature, enter it below to generate your token.\n"); + printf("Token Request Code: %s\n", req_base64_encoded); + printf("Enter your signature: "); + + if(get_fixed_length(sig_base64, sizeof(sig_base64))){ + printf("Invalid signature length\n"); + return 1; + }; + + base64_decode_oneshot(sig_base64, 64, (char*) sig.signature); + + //debug_print_bytes("Signature as hex: ", sig.signature, 48); + + if(fstoken_gen_token(&reqdesc, &sig, &token)){ + printf("Failed to generate token.\n"); + return 1; + }; + + token_length = 48 + (target.idbits + 7) / 8; + + //debug_print_bytes("Token Unblinded Signature: ", token.signature, 48); + //debug_print_bytes("TOKEN: ", &token, token_length); + + printf("\nToken: "); + print_base64(&token, token_length); + + return 0; +} + +int targinfo(char* targ_name){ int targ_status; - target_descriptor target; + fstoken_target_descriptor target; targ_status = get_target(targ_name, &target); @@ -77,12 +165,12 @@ int keydump(char* targ_name){ return 1; case 1: printf("Public Key available - can verify and request for this target\n"); - print_bytes("Public Key: ", target.pk, 96); + print_bytes("Public Key: ", target.pk, 96, '\0'); break; case 2: printf("Secret Key and Public Key available - can verify, request, and sign for this target.\n"); - print_bytes("Secret Key: ", target.sk, 32); - print_bytes("Public Key: ", target.pk, 96); + print_bytes("Secret Key: ", target.sk, 32, '\0'); + print_bytes("Public Key: ", target.pk, 96, '\0'); break; } @@ -91,7 +179,40 @@ int keydump(char* targ_name){ return 0; } -/*int keyrepair(char* target){ +int striptarg(char* targ_name_in, char* targ_name_out){ + FILE *targ_out_file; + fstoken_target_descriptor targ_out; + char* targ_out_path; + + if(!get_target(targ_name_in, &targ_out)){ + printf("Unknown input target. Exiting.\n"); + return 1; + } + + if(targ_out.sk_available == false){ + printf("Target %s already lacks a Secret Key - only targets with Secret Keys can be stripped.\n", targ_name_in); + return 2; + } + + memset(targ_out.sk, 0x00, 32); + targ_out.sk_available = false; + + get_targ_path(targ_name_out, &targ_out_path); + + targ_out_file = fopen(targ_out_path, "w"); + if(!targ_out_file){ + printf("Could not open output target file. Exiting.\n"); + return 1; + } + fwrite(&targ_out, sizeof(fstoken_target_descriptor), 1, targ_out_file); + fclose(targ_out_file); + + printf("Public-Key-only target %s succesfully created from original target %s.\n", targ_name_in, targ_name_out); + + return 0; +} + +/*int keyrepair(char* target){ // this is probably no longer necessary with single-file targets FILE *key_file; byte sk[32]; byte pk[96]; @@ -130,7 +251,7 @@ int keydump(char* targ_name){ int keygen(char* target, byte* ikm, int idbits, int hashbits){ char *targ_path; FILE *targ_file; - target_descriptor targ; + fstoken_target_descriptor targ; debug_print_bytes("IKM: ", ikm, 32); @@ -153,7 +274,7 @@ int keygen(char* target, byte* ikm, int idbits, int hashbits){ printf("Could not open target file. Exiting.\n"); return 1; } - fwrite(&targ, sizeof(targ), 1, targ_file); + fwrite(&targ, sizeof(fstoken_target_descriptor), 1, targ_file); fclose(targ_file); return 0; @@ -179,7 +300,7 @@ int list_targets(bool verbose){ int n, targname_len; struct dirent **files; char *targdir_path, *targ_path, *targ_name; - target_descriptor targ; + fstoken_target_descriptor targ; targdir_path = malloc(strlen(token_path) + 9); if(targdir_path == NULL) return 1; @@ -233,6 +354,14 @@ void bendian_from_hex_string(byte* bendian, char* string, int length){ } } +void lendian_from_hex_string(byte* lendian, char* string, int length){ + char byte[2]; + for(int i = (length-1); i >= 0; i--){ + memcpy(byte, &string[i*2], 2); + lendian[i] = strtol(byte, 0, 16); + } +} + // mostly boring command line parsing int main(int argc, char *argv[]){ token_path = getenv("FEMTOSTAR_TOKEN_PATH"); @@ -259,6 +388,7 @@ int main(int argc, char *argv[]){ if(argc > 5){ printf("Too many arguments. Exiting.\n"); + return(1); } // Make sure there's a target name @@ -292,18 +422,18 @@ int main(int argc, char *argv[]){ return 1; } - bendian_from_hex_string(ikm, argv[4], 64); + lendian_from_hex_string(ikm, argv[4], 32); } return keygen(argv[2], ikm, idbits, hashbits); - }else if(strcmp(argv[1], "keydump") == 0){ + }else if(strcmp(argv[1], "info") == 0){ // Make sure there's a target name if(argc < 3){ - fprintf(stderr, "A target name must be provided, e.g. %s keydump [targ]\n", argv[0]); + fprintf(stderr, "A target name must be provided, e.g. %s info [targ]\n", argv[0]); return(1); } - return keydump(argv[2]); + return targinfo(argv[2]); }/*else if(strcmp(argv[1], "keyrepair") == 0){ // Make sure there's a target name if(argc < 3){ @@ -312,5 +442,64 @@ int main(int argc, char *argv[]){ } return keyrepair(argv[2]); - }*/ + }*/else if(strcmp(argv[1], "strip") == 0){ + // Make sure there's a target name + if(argc < 4){ + fprintf(stderr, "Two target names must be provided, e.g. %s strip [targin] [targout]\n", argv[0]); + return(1); + } + + return striptarg(argv[2], argv[3]); + }else if(strcmp(argv[1], "ireq") == 0){ + byte blinding_factor[32]; + byte token_id[IDBYTES_MAX]; + + // Make sure there's a target name + if(argc < 3){ + fprintf(stderr, "A target name must be provided, e.g. %s req [targ]\n", argv[0]); + return(1); + } + + if(argc > 5){ + printf("Too many arguments. Exiting.\n"); + return(1); + } + + if(argc < 4){ + getrandom(token_id, IDBYTES_MAX, GRND_NONBLOCK); + }else{ + if(strlen(argv[3]) > (IDBYTES_MAX * 2)){ + fprintf(stderr, "If providing a Token ID, it must be %i bits (%i hexadecimal digits) or less \n", IDBITS_MAX, IDBYTES_MAX * 2); + return 1; + } + + lendian_from_hex_string(token_id, argv[3], (strlen(argv[3]) + 1) / 2); + } + + // If no IKM is provided, use the system true randomness source + if(argc < 5){ + getrandom(blinding_factor, 32, GRND_NONBLOCK); + }else{ + if(strlen(argv[4]) != 64){ + fprintf(stderr, "If providing a blinding factor, it must be 32 bytes (64 hexadecimal digits)\n"); + return 1; + } + + lendian_from_hex_string(blinding_factor, argv[4], 32); + } + + return interactive_request(argv[2], token_id, blinding_factor); + }else if(strcmp(argv[1], "sign") == 0){ + if(argc != 4){ + fprintf(stderr, "Please provide a target [targ] and a Token Request Code [req]. See help.\n"); + return 1; + } + + if(strlen(argv[3]) != 64){ + fprintf(stderr, "Token request code must be 48 bytes (64 base64 characters).\n"); + return 1; + } + + sign_single(argv[2], argv[3]); + } } \ No newline at end of file diff --git a/debugprint.c b/debugprint.c index fb885c2..8f1be37 100644 --- a/debugprint.c +++ b/debugprint.c @@ -1,18 +1,34 @@ #include +#include #include "debugprint.h" #include "blst/blst.h" +#include "base64.h" -void print_bytes(const char* label, void *toprint, int length){ +void print_bytes(const char* label, void *toprint, int length, char separator){ printf("%s", label); for(int i=0;iidbits + 7)/8; + + //printf("IDBYTES_MAX: %i, IDBYTES for target: %i\n", IDBYTES_MAX, targ_idbytes); + + memcpy(&reqdesc->blinding_factor, blinding_factor, 32); + + bit_truncated_copy(token_id, reqdesc->token_id, IDBYTES_MAX, target->idbits); //cut down the supplied token ID to match the target's IDBITS value + + //debug_print_bytes("Working with ID: ", reqdesc->token_id, IDBYTES_MAX); + //debug_print_bytes("Working with Blinding Factor: ", blinding_factor, 32); + + // Hash the Token ID to a BLST P1 + blst_hash_to_g1(&hash, token_id, targ_idbytes, FSTOKEN_DST, strlen((char *) FSTOKEN_DST), target->pk, 96); + + // Get the blinding factor as a BLST scalar + blst_scalar_from_bendian(&blinding_r, blinding_factor); + + blst_p1_serialize(debug_print_buf, &hash); + //debug_print_bytes("HASH: ", debug_print_buf, 96); + + // Sign the hash as if the blinding factor was a private key + blst_sign_pk_in_g2(&blinded_id, &hash, &blinding_r); + + // Compress the resulting signature and put it in the signature request + blst_p1_compress(req->id_blind, &blinded_id); + + //debug_print_bytes("Blinded and compressed for wire: ", req->id_blind, 48); +} + +int fstoken_sign_request(fstoken_request* req, fstoken_signature* sig, fstoken_target_descriptor* targ){ + blst_scalar sk; + blst_p1 req_id, signature; + blst_p1_affine req_id_affine; + + // get the secret key as a scalar + blst_scalar_from_bendian(&sk, targ->sk); + + // Deserialize the request - it's already a serialized P1 point, we don't need to (literally) rehash it + blst_p1_uncompress(&req_id_affine, req->id_blind); + + // i do not know why deserializing always gives you affine points + blst_p1_from_affine(&req_id, &req_id_affine); + + // Return 1 if the point isn't in G1, this is basically a sanity check + if(!blst_p1_in_g1(&req_id)) return 1; + + // sign with it + blst_sign_pk_in_g2(&signature, &req_id, &sk); + + // Compress and print the signature + blst_p1_compress(sig->signature, &signature); + + //debug_print_bytes("Compressed signature:", sig->signature, 48); + + return 0; +} + +int fstoken_gen_token(fstoken_request_descriptor* req, fstoken_signature* sig, fstoken_token* token){ + blst_p1 returned_signature, unblinded_signature; + blst_p1_affine returned_signature_affine; + blst_scalar blinding_r, inverse_r; + + // We now have the signature back. returned_signature is a blst_p1_affine because this is pk_in_g2. + blst_p1_uncompress(&returned_signature_affine, sig->signature); + + // Convert the decompressed returned signature from an affine to a P1 + blst_p1_from_affine(&returned_signature, &returned_signature_affine); + + // Confirm the signature point is in the G1 group + if(!blst_p1_in_g1(&returned_signature)) return 1; + + // Retreive the blinding factor and take its inverse + blst_scalar_from_bendian(&blinding_r, req->blinding_factor); + blst_sk_inverse(&inverse_r, &blinding_r); + + //print_scalar("Inverse R: ", &inverse_r); + + // Unblind the signature using the inverse of the blinding factor + blst_sign_pk_in_g2(&unblinded_signature, &returned_signature, &inverse_r); + + memcpy(token->token_id, req->token_id, IDBYTES_MAX); + blst_p1_compress(token->signature, &unblinded_signature); + + return 0; +} + +void bit_truncated_copy(byte* src, byte* dest, size_t destlen, int bits){ + uint8_t bitmask; + int bytes; + + bytes = (bits + 7) / 8; + + memset(dest, 0, destlen); //clear out the destination + memcpy(dest, src, bytes); //copy over as many bytes as are needed for the number of bits + + //bitmask out the high (fsp is little-endian) bits of the destination if bits is not a round number of bytes + bitmask = 0xFF >> (bits % 8); + //printf("mask: 0x%0x\n", bitmask); + dest[bytes - 1] = dest[bytes - 1] & bitmask; } \ No newline at end of file diff --git a/fstoken.h b/fstoken.h index 9261bba..a888ba3 100644 --- a/fstoken.h +++ b/fstoken.h @@ -3,16 +3,33 @@ #include "blst/blst.h" #include #include +#include +// only used for debugging +#include +#include "debugprint.h" + +// Note: In constrained environments where the IDBITS and HASHBITS values of the tokens in use are known, such as in fsp, these can be set exactly. +// However, that may render some structs (such as the request descriptor) incompatible if moved to an environment where this is nonstandard. #define IDBITS_DEFAULT 128 #define IDBITS_MAX 256 #define HASHBITS_DEFAULT 256 #define HASHBITS_MAX 256 +#define IDBYTES_MAX (IDBITS_MAX + 7) / 8 +#define HASHBYTES_MAX (HASHBITS_MAX + 7) / 8 + +#define FSTOKEN_DST ((byte*) "MY-DST") + // For efficiency's sake, structs intended for use with the FemtoStar Protocol must be strictly-aligned without padding, with little-endian byte ordering. // This avoids wasting network capacity on padding, and avoids the performance impact of reordering on most modern platforms (most importantly x86 and RISC-V) // while ensuring compatibility with strict-alignment architectures. Take note: This is NOT typical network-order! +// Additionally, note that most structs do not explicitly include what target they are for (even the target descriptor!). Keeping what is for what target is +// not a core part of fstoken, and is the responsibility of the software using fstoken (e.g. ctm or an implementation of fsp). This keeps tokens light in +// situations where which target is relevant is unambiguous (e.g. in the session setup with a satellite, where the satellite is always the same target). + +// A target_descriptor is a stored description of a target, which can be used to request, verify, and sometimes sign tokens for it. typedef struct{ #pragma scalar_storage_order little-endian @@ -22,9 +39,42 @@ typedef struct{ uint16_t hashbits; byte sk[32]; byte pk[96]; -} target_descriptor; +} fstoken_target_descriptor; + +// A request descriptor is a structure held privately by the requester until they receive a signature for their request - it is then used to generate a token +typedef struct{ + #pragma scalar_storage_order little-endian + + byte blinding_factor[32]; + byte token_id[IDBYTES_MAX]; +} fstoken_request_descriptor; + +// A request is a structure sent by the requester to the signer, to be signed and a signature returned. It need not be kept private. +typedef struct{ + #pragma scalar_storage_order little-endian + + byte id_blind[48]; +} fstoken_request; + +// I'm unsure if these "single-item structs" make any sense - signatures in blst are independent of system or scalar_storage_order endianness anyway +typedef struct{ + #pragma scalar_storage_order little-endian + + byte signature[48]; +} fstoken_signature; + +typedef struct{ + #pragma scalar_storage_order little-endian + + byte signature[48]; + byte token_id[IDBYTES_MAX]; +} fstoken_token; void fstoken_keygen(byte* ikm, byte* sk_byte, byte* pk_byte); void fstoken_get_pk_from_sk(byte* sk_byte, byte* pk_byte); +void bit_truncated_copy(byte* src, byte* dest, size_t destlen, int bits); +void fstoken_gen_request(fstoken_request* req, fstoken_request_descriptor* reqdesc, fstoken_target_descriptor* target, byte* token_id, byte* blinding_factor); +int fstoken_sign_request(fstoken_request* req, fstoken_signature* sig, fstoken_target_descriptor* targ); +int fstoken_gen_token(fstoken_request_descriptor* req, fstoken_signature* sig, fstoken_token* token); #endif \ No newline at end of file