Зафиксировать все текущие изменения проекта

This commit is contained in:
AidarKC
2026-06-04 22:27:09 +04:00
parent 624557ebfd
commit 60049442f1
9 changed files with 1526 additions and 182 deletions
@@ -9,6 +9,7 @@
- `audio` — динамик/аудио-кодек (пример `07_ES8311`)
- `hello` — базовый тест экрана (пример `01_HelloWorld`)
- `simple` — простой кастомный тест: экран + touch + запись/проигрывание + наклон (IMU)
- `argon2` — генерация masterSecret через Argon2id с SD-картой как памятью (тест скорости)
Запуск:
@@ -0,0 +1,943 @@
/*
* argon2_sd_test.ino v2
* Генерация masterSecret (Argon2id) + 3 Ed25519 ключевых пары.
* Результат сохраняется в NVS (внутренняя flash ESP32).
*
* Алгоритм совпадает с JS crypto-utils.js:
* salt = SHA256("shine-auth-v2|login=<login>|suffix=master.secret")[0:16]
* passBytes = UTF8("<login>\n<password>")
* secret = Argon2id(passBytes, salt, t=2, m=65536 KB, p=1, dkLen=32)
* keyPair_i = Ed25519(SHA256(base64(secret) + "|" + suffix_i))
* suffixes = ["root.key", "bch.key", "dev.key"]
*
* Плата: Waveshare ESP32-S3-Touch-AMOLED-2.16
* SD : SDMMC 1-bit CLK=GPIO2, CMD=GPIO1, D0=GPIO3
*/
#include <Arduino.h>
#include <Wire.h>
#include <string.h>
#include <stdint.h>
#include "argon2_types.h"
#include "driver/gpio.h"
#include "Arduino_GFX_Library.h"
#include "TouchDrvCSTXXX.hpp"
#include "SD_MMC.h"
#include "mbedtls/sha256.h"
#include "mbedtls/base64.h"
#include <Ed25519.h>
#include <Preferences.h>
// ═══════════════════════════════════════════════════════════
// PINS
// ═══════════════════════════════════════════════════════════
#define PIN_LCD_CS 12
#define PIN_LCD_SCLK 38
#define PIN_LCD_D0 4
#define PIN_LCD_D1 5
#define PIN_LCD_D2 6
#define PIN_LCD_D3 7
#define PIN_LCD_RST 2
#define PIN_I2C_SDA 15
#define PIN_I2C_SCL 14
#define PIN_TP_INT 11
#define PIN_SD_CLK 2
#define PIN_SD_CMD 1
#define PIN_SD_D0 3
// ═══════════════════════════════════════════════════════════
// DISPLAY
// ═══════════════════════════════════════════════════════════
#define DISP_W 480
#define DISP_H 480
Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
PIN_LCD_CS, PIN_LCD_SCLK,
PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
gBus, PIN_LCD_RST, 0, DISP_W, DISP_H, 0, 0, 0, 0);
// ═══════════════════════════════════════════════════════════
// TOUCH (полинг + прерывание)
// ═══════════════════════════════════════════════════════════
TouchDrvCST92xx gTouch;
volatile bool gTouchIrq = false;
void IRAM_ATTR onTouchIrq() { gTouchIrq = true; }
// Состояние свайпа
static bool gTouching = false;
static int16_t gTouchStartY = 0;
static int16_t gTouchPrevY = 0;
static int16_t gTouchCurY = 0;
static int16_t gTouchCurX = 0;
// ═══════════════════════════════════════════════════════════
// COLORS
// ═══════════════════════════════════════════════════════════
#define C_BG 0x0000u
#define C_KEY 0x2104u
#define C_KEY_HL 0x4208u
#define C_TEXT 0xFFFFu
#define C_HINT 0x7BEFu
#define C_GREEN 0x07E0u
#define C_RED 0xF800u
#define C_FIELD 0x18C3u
#define C_BAR_BG 0x18C3u
#define C_BAR_FG 0xFFFFu
#define C_SEP 0x2945u
#define C_ACCENT 0x05FFu
// ═══════════════════════════════════════════════════════════
// APP STATE
// ═══════════════════════════════════════════════════════════
enum AppState {
ST_LOGIN, // ввод логина
ST_PASS, // ввод пароля
ST_RUNNING, // вычисление
ST_DONE, // результат (только что посчитан)
ST_SAVED, // результат (загружен из NVS)
ST_CONFIRM, // диалог "сгенерировать новый?"
ST_ERROR
};
static AppState gState = ST_LOGIN;
static char gLogin[64] = {};
static char gPass[64] = {};
static uint8_t gSecret[32];
static char gSecretHex[65];
static bool gKbNums = false;
// ═══════════════════════════════════════════════════════════
// КЛЮЧЕВЫЕ ПАРЫ
// ═══════════════════════════════════════════════════════════
struct KeyPair { uint8_t pub[32]; uint8_t priv[32]; };
static KeyPair gKeys[3];
static const char * KEY_SUFFIXES[3] = {"root.key", "bch.key", "dev.key"};
static const char * KEY_LABELS[3] = {"root.key", "bch.key", "dev.key"};
static uint32_t gElapsedSec = 0;
// Base58 представления (43-44 символа для 32 байт + \0)
static char gSecretB58[50];
static char gPubB58[3][50];
static char gPrivB58[3][50];
// ═══════════════════════════════════════════════════════════
// ARGON2 ПАРАМЕТРЫ
// ═══════════════════════════════════════════════════════════
#define A2_T 2u
#define A2_M 65536u
#define A2_P 1u
#define A2_DKLEN 32u
#define A2_SYNC 4u
#define A2_SEG (A2_M / A2_SYNC)
#define A2_VERSION 0x13u
#define A2_TYPE 2u
#define A2_BLKSZ 1024u
#define TOTAL_FILLS (A2_T * A2_M - 2u)
static uint32_t gDone = 0;
static uint32_t gStartMs = 0;
// ─── прогресс-бар: антимерцание ───────────────────────────
static int gBarFilledPx = 0; // сколько пикселей уже зелёных
static uint32_t gLastElSec = 0xFFFFFFFF; // последнее нарисованное время
// ═══════════════════════════════════════════════════════════
// ВЫЧИСЛИТЕЛЬНОЕ СОСТОЯНИЕ
// ═══════════════════════════════════════════════════════════
static uint8_t gH0[64];
static uint32_t gCurPass = 0;
static uint32_t gCurBlock = 2;
static bool gInitDone = false;
static uint8_t *gBufPrev = nullptr;
static uint8_t *gBufRef = nullptr;
static uint8_t *gBufOut = nullptr;
static uint8_t *gBufZero = nullptr;
static uint8_t *gBufAddr = nullptr;
static uint32_t *gJ1Seg = nullptr;
#define SD_MEM_FILE "/argon2.bin"
static File gSdFile;
// ═══════════════════════════════════════════════════════════
// РЕЗУЛЬТАТ — прокрутка
// ═══════════════════════════════════════════════════════════
#define RES_BTN_H 52 // высота кнопки "Новый" внизу
#define RES_VIEW_H (DISP_H - RES_BTN_H) // 428 px
static int32_t gScrollY = 0;
static int32_t gScrollMax = 0; // рассчитывается при отрисовке
// NVS
static Preferences gPrefs;
// ═══════════════════════════════════════════════════════════
// BLAKE2b
// ═══════════════════════════════════════════════════════════
#define ROTR64(x,n) (((x)>>(n))|((x)<<(64-(n))))
static const uint64_t B2IV[8] = {
0x6A09E667F3BCC908ULL,0xBB67AE8584CAA73BULL,
0x3C6EF372FE94F82BULL,0xA54FF53A5F1D36F1ULL,
0x510E527FADE682D1ULL,0x9B05688C2B3E6C1FULL,
0x1F83D9ABFB41BD6BULL,0x5BE0CD19137E2179ULL
};
static const uint8_t B2SIGMA[12][16] = {
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3},
{11,8,12,0,5,2,15,13,10,14,3,6,7,1,9,4},
{7,9,3,1,13,12,11,14,2,6,5,10,4,0,15,8},
{9,0,5,7,2,4,10,15,14,1,11,12,6,8,3,13},
{2,12,6,10,0,11,8,3,4,13,7,5,15,14,1,9},
{12,5,1,15,14,13,4,10,0,7,6,3,9,2,8,11},
{13,11,7,14,12,1,3,9,5,0,15,4,8,6,2,10},
{6,15,14,9,11,3,0,8,12,2,13,7,1,4,10,5},
{10,2,8,4,7,6,1,5,15,11,9,14,3,12,13,0},
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},
{14,10,4,8,9,15,13,6,1,12,0,2,11,7,5,3}
};
static B2State gB2S;
static void b2_compress(B2State *S, const uint8_t *blk) {
uint64_t m[16],v[16];
for (int i=0;i<16;i++) m[i]=((const uint64_t*)blk)[i];
for (int i=0;i<8;i++) v[i]=S->h[i];
v[8]=B2IV[0];v[9]=B2IV[1];v[10]=B2IV[2];v[11]=B2IV[3];
v[12]=B2IV[4]^S->t[0];v[13]=B2IV[5]^S->t[1];
v[14]=B2IV[6]^S->f[0];v[15]=B2IV[7]^S->f[1];
#define BG(r,i,a,b,c,d) \
v[a]+=v[b]+m[B2SIGMA[r][2*(i)]]; v[d]=ROTR64(v[d]^v[a],32); \
v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],24); \
v[a]+=v[b]+m[B2SIGMA[r][2*(i)+1]];v[d]=ROTR64(v[d]^v[a],16); \
v[c]+=v[d]; v[b]=ROTR64(v[b]^v[c],63)
for (int r=0;r<12;r++){
BG(r,0,0,4,8,12);BG(r,1,1,5,9,13);BG(r,2,2,6,10,14);BG(r,3,3,7,11,15);
BG(r,4,0,5,10,15);BG(r,5,1,6,11,12);BG(r,6,2,7,8,13);BG(r,7,3,4,9,14);
}
#undef BG
for (int i=0;i<8;i++) S->h[i]^=v[i]^v[i+8];
}
static void b2_init(B2State *S,size_t outlen){
memset(S,0,sizeof(*S));memcpy(S->h,B2IV,64);
S->h[0]^=0x01010000ULL|outlen;S->outlen=outlen;
}
static void b2_update(B2State *S,const uint8_t *in,size_t inlen){
while(inlen>0){
size_t fill=128-S->buflen,use=inlen<fill?inlen:fill;
memcpy(S->buf+S->buflen,in,use);S->buflen+=use;in+=use;inlen-=use;
if(S->buflen==128&&inlen>0){
S->t[0]+=128;if(S->t[0]<128)S->t[1]++;
b2_compress(S,S->buf);S->buflen=0;
}
}
}
static void b2_final(B2State *S,uint8_t *digest){
S->f[0]=~0ULL;S->t[0]+=S->buflen;if(S->t[0]<S->buflen)S->t[1]++;
memset(S->buf+S->buflen,0,128-S->buflen);b2_compress(S,S->buf);
uint8_t tmp[64];
for(int i=0;i<8;i++){uint64_t w=S->h[i];for(int j=0;j<8;j++)tmp[i*8+j]=(uint8_t)(w>>(j*8));}
memcpy(digest,tmp,S->outlen);
}
static void b2hash(const uint8_t *in,size_t inlen,uint8_t *digest,size_t outlen){
b2_init(&gB2S,outlen);b2_update(&gB2S,in,inlen);b2_final(&gB2S,digest);
}
static void b2long(const uint8_t *in,size_t inlen,uint8_t *digest,uint32_t outlen){
uint8_t lenle[4]={(uint8_t)outlen,(uint8_t)(outlen>>8),(uint8_t)(outlen>>16),(uint8_t)(outlen>>24)};
if(outlen<=64){b2_init(&gB2S,outlen);b2_update(&gB2S,lenle,4);b2_update(&gB2S,in,inlen);b2_final(&gB2S,digest);return;}
uint8_t tmp[64];
b2_init(&gB2S,64);b2_update(&gB2S,lenle,4);b2_update(&gB2S,in,inlen);b2_final(&gB2S,tmp);
memcpy(digest,tmp,32);digest+=32;uint32_t rem=outlen-32;
while(rem>64){b2hash(tmp,64,tmp,64);memcpy(digest,tmp,32);digest+=32;rem-=32;}
b2hash(tmp,64,tmp,rem);memcpy(digest,tmp,rem);
}
// ═══════════════════════════════════════════════════════════
// ARGON2ID — BLOCK FILL
// ═══════════════════════════════════════════════════════════
#define A2G(a,b,c,d) do{ \
(a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \
(d)=ROTR64((d)^(a),32); \
(c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \
(b)=ROTR64((b)^(c),24); \
(a)+=(b)+2*((a)&0xFFFFFFFFULL)*((b)&0xFFFFFFFFULL); \
(d)=ROTR64((d)^(a),16); \
(c)+=(d)+2*((c)&0xFFFFFFFFULL)*((d)&0xFFFFFFFFULL); \
(b)=ROTR64((b)^(c),63); \
}while(0)
#define A2P(v) do{ \
A2G((v)[0],(v)[4],(v)[8],(v)[12]);A2G((v)[1],(v)[5],(v)[9],(v)[13]); \
A2G((v)[2],(v)[6],(v)[10],(v)[14]);A2G((v)[3],(v)[7],(v)[11],(v)[15]); \
A2G((v)[0],(v)[5],(v)[10],(v)[15]);A2G((v)[1],(v)[6],(v)[11],(v)[12]); \
A2G((v)[2],(v)[7],(v)[8],(v)[13]);A2G((v)[3],(v)[4],(v)[9],(v)[14]); \
}while(0)
static void fillBlock(uint32_t prevIdx,uint32_t refIdx,uint32_t outIdx,bool xorMode){
gSdFile.seek((uint64_t)prevIdx*A2_BLKSZ);gSdFile.read(gBufPrev,A2_BLKSZ);
gSdFile.seek((uint64_t)refIdx*A2_BLKSZ);gSdFile.read(gBufRef,A2_BLKSZ);
uint64_t *R=(uint64_t*)gBufOut,*P=(uint64_t*)gBufPrev,*F=(uint64_t*)gBufRef;
for(int i=0;i<128;i++)R[i]=P[i]^F[i];
uint64_t *Q=(uint64_t*)gBufRef;memcpy(Q,R,A2_BLKSZ);
for(int j=0;j<8;j++)A2P(&Q[16*j]);
uint64_t row[16];
for(int i=0;i<8;i++){
for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
A2P(row);
for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
}
for(int i=0;i<128;i++)R[i]=Q[i]^R[i];
if(xorMode){
gSdFile.seek((uint64_t)outIdx*A2_BLKSZ);gSdFile.read(gBufPrev,A2_BLKSZ);
uint64_t *O=(uint64_t*)gBufPrev;for(int i=0;i<128;i++)R[i]^=O[i];
}
gSdFile.seek((uint64_t)outIdx*A2_BLKSZ);gSdFile.write(gBufOut,A2_BLKSZ);
}
static void generateAddresses(uint32_t pass,uint32_t slice){
memset(gBufZero,0,A2_BLKSZ);
uint8_t inputBlk[A2_BLKSZ];memset(inputBlk,0,A2_BLKSZ);
uint64_t *iv=(uint64_t*)inputBlk;
iv[0]=pass;iv[1]=0;iv[2]=slice;iv[3]=A2_M;iv[4]=A2_T;iv[5]=A2_TYPE;
uint32_t count=0;
for(uint32_t b=0;b<A2_SEG;b+=128){
iv[6]=++count;
{
uint64_t *R=(uint64_t*)gBufAddr,*Z=(uint64_t*)gBufZero,*I=iv;
for(int i=0;i<128;i++)R[i]=Z[i]^I[i];
uint64_t *Q=R,row[16];
for(int j=0;j<8;j++)A2P(&Q[16*j]);
for(int i=0;i<8;i++){
for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
A2P(row);
for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
}
for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^I[i]);
}
{
uint64_t *R=(uint64_t*)gBufPrev,*Z=(uint64_t*)gBufZero,*A=(uint64_t*)gBufAddr;
for(int i=0;i<128;i++)R[i]=Z[i]^A[i];
uint64_t *Q=R,row[16];
for(int j=0;j<8;j++)A2P(&Q[16*j]);
for(int i=0;i<8;i++){
for(int k=0;k<8;k++){row[2*k]=Q[2*i+16*k];row[2*k+1]=Q[2*i+16*k+1];}
A2P(row);
for(int k=0;k<8;k++){Q[2*i+16*k]=row[2*k];Q[2*i+16*k+1]=row[2*k+1];}
}
for(int i=0;i<128;i++)R[i]=Q[i]^(Z[i]^A[i]);
memcpy(gBufAddr,gBufPrev,A2_BLKSZ);
}
uint64_t *addr=(uint64_t*)gBufAddr;
uint32_t cnt=(b+128<=A2_SEG)?128:(A2_SEG-b);
for(uint32_t j=0;j<cnt;j++)gJ1Seg[b+j]=(uint32_t)(addr[j]&0xFFFFFFFFULL);
}
}
static uint32_t indexAlpha(uint32_t pass,uint32_t slice,uint32_t posInSlice,uint32_t J1){
uint32_t refAreaSize;
if(pass==0){
if(slice==0){uint32_t pos=posInSlice;refAreaSize=(pos<2)?0:pos-1;}
else refAreaSize=slice*A2_SEG+posInSlice-1;
}else refAreaSize=A2_M-A2_SEG+posInSlice-1;
if(refAreaSize==0)refAreaSize=1;
uint64_t relPos=(uint64_t)J1*J1>>32;
relPos=refAreaSize-1-((uint64_t)refAreaSize*relPos>>32);
uint32_t startPos=0;
if(pass!=0&&slice!=A2_SYNC-1)startPos=(slice+1)*A2_SEG;
return (startPos+relPos)%A2_M;
}
// ═══════════════════════════════════════════════════════════
// SHA-256
// ═══════════════════════════════════════════════════════════
static void sha256calc(const uint8_t *in,size_t len,uint8_t *out32){
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);mbedtls_sha256_starts(&ctx,0);
mbedtls_sha256_update(&ctx,in,len);mbedtls_sha256_finish(&ctx,out32);
mbedtls_sha256_free(&ctx);
}
// ═══════════════════════════════════════════════════════════
// BASE58
// ═══════════════════════════════════════════════════════════
static const char B58_ALPHA[] =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
static void bytesToBase58(const uint8_t *data,size_t len,char *out,size_t outSz){
int zeros=0;
while(zeros<(int)len&&data[zeros]==0)zeros++;
uint8_t tmp[64]={};int tmpLen=0;
for(int i=zeros;i<(int)len;i++){
int carry=data[i];
for(int j=0;j<tmpLen;j++){carry+=256*tmp[j];tmp[j]=carry%58;carry/=58;}
while(carry){tmp[tmpLen++]=carry%58;carry/=58;}
}
int n=0;
for(int i=0;i<zeros&&n<(int)outSz-1;i++)out[n++]='1';
for(int i=tmpLen-1;i>=0&&n<(int)outSz-1;i--)out[n++]=B58_ALPHA[(int)tmp[i]];
out[n]='\0';
}
// ═══════════════════════════════════════════════════════════
// KEY DERIVATION (Ed25519 из masterSecret)
// ═══════════════════════════════════════════════════════════
static void deriveKeyPairs(){
// base64(masterSecret) — standard base64 с '+','/'
uint8_t b64buf[48];size_t b64len=0;
mbedtls_base64_encode(b64buf,sizeof(b64buf),&b64len,gSecret,32);
b64buf[b64len]='\0';
for(int i=0;i<3;i++){
char material[96];
snprintf(material,sizeof(material),"%s|%s",(char*)b64buf,KEY_SUFFIXES[i]);
uint8_t seed[32];
sha256calc((uint8_t*)material,strlen(material),seed);
memcpy(gKeys[i].priv,seed,32);
Ed25519::derivePublicKey(gKeys[i].pub,seed);
}
// base58 для отображения
bytesToBase58(gSecret,32,gSecretB58,sizeof(gSecretB58));
for(int i=0;i<3;i++){
bytesToBase58(gKeys[i].pub,32,gPubB58[i],sizeof(gPubB58[i]));
bytesToBase58(gKeys[i].priv,32,gPrivB58[i],sizeof(gPrivB58[i]));
}
}
// ═══════════════════════════════════════════════════════════
// NVS
// ═══════════════════════════════════════════════════════════
static void nvsSave(){
gPrefs.begin("shine",false);
gPrefs.putString("login",gLogin);
gPrefs.putBytes("secret",gSecret,32);
for(int i=0;i<3;i++){
char k[6];
snprintf(k,sizeof(k),"pub%d",i); gPrefs.putBytes(k,gKeys[i].pub,32);
snprintf(k,sizeof(k),"prv%d",i); gPrefs.putBytes(k,gKeys[i].priv,32);
}
gPrefs.putUInt("tsec",gElapsedSec);
gPrefs.putBool("valid",true);
gPrefs.end();
}
static bool nvsLoad(){
gPrefs.begin("shine",true);
bool valid=gPrefs.getBool("valid",false);
if(valid){
gPrefs.getString("login",gLogin,sizeof(gLogin));
gPrefs.getBytes("secret",gSecret,32);
for(int i=0;i<3;i++){
char k[6];
snprintf(k,sizeof(k),"pub%d",i); gPrefs.getBytes(k,gKeys[i].pub,32);
snprintf(k,sizeof(k),"prv%d",i); gPrefs.getBytes(k,gKeys[i].priv,32);
}
gElapsedSec=gPrefs.getUInt("tsec",0);
for(int i=0;i<32;i++)snprintf(gSecretHex+i*2,3,"%02x",gSecret[i]);
// base58
bytesToBase58(gSecret,32,gSecretB58,sizeof(gSecretB58));
for(int i=0;i<3;i++){
bytesToBase58(gKeys[i].pub,32,gPubB58[i],sizeof(gPubB58[i]));
bytesToBase58(gKeys[i].priv,32,gPrivB58[i],sizeof(gPrivB58[i]));
}
}
gPrefs.end();
return valid;
}
static void nvsClear(){
gPrefs.begin("shine",false);
gPrefs.clear();
gPrefs.end();
}
// ═══════════════════════════════════════════════════════════
// ARGON2ID INIT
// ═══════════════════════════════════════════════════════════
static void argon2Init(const char *login,const char *password){
char saltSrc[256];
snprintf(saltSrc,sizeof(saltSrc),"shine-auth-v2|login=%s|suffix=master.secret",login);
uint8_t saltHash[32];sha256calc((uint8_t*)saltSrc,strlen(saltSrc),saltHash);
uint8_t salt[16];memcpy(salt,saltHash,16);
char passBytes[128];
snprintf(passBytes,sizeof(passBytes),"%s\n%s",login,password);
uint32_t passLen=strlen(passBytes),saltLen=16;
b2_init(&gB2S,64);
uint8_t le4[4];
#define B2LE32(val) do{uint32_t _v=(val);le4[0]=_v;le4[1]=_v>>8;le4[2]=_v>>16;le4[3]=_v>>24;b2_update(&gB2S,le4,4);}while(0)
B2LE32(A2_P);B2LE32(A2_DKLEN);B2LE32(A2_M);B2LE32(A2_T);
B2LE32(A2_VERSION);B2LE32(A2_TYPE);
B2LE32(passLen);b2_update(&gB2S,(const uint8_t*)passBytes,passLen);
B2LE32(saltLen);b2_update(&gB2S,salt,saltLen);
B2LE32(0);B2LE32(0);
#undef B2LE32
b2_final(&gB2S,gH0);
uint8_t input[72];memcpy(input,gH0,64);
for(uint32_t i=0;i<2;i++){
input[64]=i;input[65]=0;input[66]=0;input[67]=0;
input[68]=0;input[69]=0;input[70]=0;input[71]=0;
b2long(input,72,gBufOut,A2_BLKSZ);
gSdFile.seek((uint64_t)i*A2_BLKSZ);gSdFile.write(gBufOut,A2_BLKSZ);
}
gCurPass=0;gCurBlock=2;gDone=0;gStartMs=millis();
gBarFilledPx=0;gLastElSec=0xFFFFFFFF;
gInitDone=true;
generateAddresses(0,0);
}
// ═══════════════════════════════════════════════════════════
// ARGON2ID STEP
// ═══════════════════════════════════════════════════════════
static bool argon2Step(){
if(gCurPass>=A2_T)return true;
uint32_t blk=gCurBlock;
uint32_t slice=blk/A2_SEG;
uint32_t posInSlice=blk-slice*A2_SEG;
bool xorMode=(gCurPass>0);
uint32_t J1;
bool useArgon2i=(gCurPass==0&&slice<2);
if(useArgon2i){
J1=gJ1Seg[posInSlice];
}else{
uint32_t prevIdx=(blk==0)?A2_M-1:blk-1;
gSdFile.seek((uint64_t)prevIdx*A2_BLKSZ);gSdFile.read(gBufRef,8);
J1=((uint32_t*)gBufRef)[0];
}
uint32_t prevIdx=(blk==0)?A2_M-1:blk-1;
uint32_t refIdx=indexAlpha(gCurPass,slice,posInSlice,J1);
fillBlock(prevIdx,refIdx,blk,xorMode);
gDone++;
gCurBlock++;
if(gCurBlock>=A2_M){
gCurBlock=0;gCurPass++;
}else{
uint32_t newSlice=gCurBlock/A2_SEG;
if(newSlice!=slice&&gCurPass==0&&newSlice<2)
generateAddresses(gCurPass,newSlice);
}
return(gCurPass>=A2_T);
}
// ═══════════════════════════════════════════════════════════
// ARGON2ID FINALIZE
// ═══════════════════════════════════════════════════════════
static void argon2Finalize(){
gSdFile.seek((uint64_t)(A2_M-1)*A2_BLKSZ);
gSdFile.read(gBufOut,A2_BLKSZ);
b2long(gBufOut,A2_BLKSZ,gSecret,A2_DKLEN);
for(int i=0;i<32;i++)snprintf(gSecretHex+i*2,3,"%02x",gSecret[i]);
gSecretHex[64]='\0';
gElapsedSec=(millis()-gStartMs)/1000;
deriveKeyPairs();
nvsSave();
Serial.println("=== RESULT ===");
Serial.printf("Login : %s\n",gLogin);
Serial.printf("Secret: %s\n",gSecretHex);
for(int i=0;i<3;i++){
Serial.printf("%s pub : %s\n",KEY_LABELS[i],gPubB58[i]);
Serial.printf("%s priv: %s\n",KEY_LABELS[i],gPrivB58[i]);
}
Serial.println("=== END ===");
}
// ═══════════════════════════════════════════════════════════
// UI — HELPERS
// ═══════════════════════════════════════════════════════════
static void uiText(int x,int y,const char *s,uint16_t color,uint8_t sz=2){
gfx->setTextColor(color);gfx->setTextSize(sz);gfx->setCursor(x,y);gfx->print(s);
}
static void uiRect(int x,int y,int w,int h,uint16_t fill,uint16_t border=0,int r=4){
gfx->fillRoundRect(x,y,w,h,r,fill);
if(border)gfx->drawRoundRect(x,y,w,h,r,border);
}
// ═══════════════════════════════════════════════════════════
// UI — KEYBOARD
// ═══════════════════════════════════════════════════════════
static const char *KB_ROWS[3]={"qwertyuiop","asdfghjkl","zxcvbnm"};
static const char *KB_NUMS[3]={"1234567890","@#$%^&*()-",".,:!?/;\"'="};
#define KB_Y0 200
#define KB_KH 44
#define KB_KW 44
#define KB_GAP 2
static void drawKeyboard(){
gfx->fillRect(0,KB_Y0-4,DISP_W,DISP_H-KB_Y0+4,C_BG);
for(int row=0;row<3;row++){
const char *keys=gKbNums?KB_NUMS[row]:KB_ROWS[row];
int n=strlen(keys);
int totalW=n*(KB_KW+KB_GAP)-KB_GAP;
int x0=(DISP_W-totalW)/2,y=KB_Y0+row*(KB_KH+KB_GAP);
for(int k=0;k<n;k++){
int x=x0+k*(KB_KW+KB_GAP);
uiRect(x,y,KB_KW,KB_KH,C_KEY,C_KEY_HL);
char tmp[3]={0};tmp[0]=keys[k];
uiText(x+KB_KW/2-6,y+KB_KH/2-8,tmp,C_TEXT,2);
}
}
int y4=KB_Y0+3*(KB_KH+KB_GAP);
uiRect(20,y4,80,KB_KH,C_KEY_HL);uiText(28,y4+12,"123",C_TEXT,2);
uiRect(108,y4,164,KB_KH,C_KEY,C_KEY_HL);uiText(162,y4+12,"_",C_TEXT,2);
uiRect(280,y4,80,KB_KH,C_KEY,C_KEY_HL);uiText(296,y4+12,"<<",C_TEXT,2);
uiRect(368,y4,92,KB_KH,C_GREEN);uiText(384,y4+12,"OK",C_TEXT,2);
}
static void drawInputScreen(const char *title,const char *buf,bool isMasked){
gfx->fillScreen(C_BG);
gfx->setTextSize(2);uiText(20,20,title,C_HINT);
uiRect(20,60,DISP_W-40,52,C_FIELD,C_KEY_HL);
char show[68];
if(isMasked){int n=strlen(buf);if(n>64)n=64;for(int i=0;i<n;i++)show[i]='*';show[n]='\0';}
else{strncpy(show,buf,67);show[67]='\0';}
gfx->setTextSize(2);gfx->setTextColor(C_TEXT);gfx->setCursor(30,78);gfx->print(show);
int cx=30+strlen(show)*12;gfx->drawFastVLine(cx,78,20,C_GREEN);
drawKeyboard();
}
static bool kbHit(int tx,int ty,int kx,int ky,int kw,int kh){
return tx>=kx&&tx<kx+kw&&ty>=ky&&ty<ky+kh;
}
static char kbTouch(int tx,int ty){
for(int row=0;row<3;row++){
const char *keys=gKbNums?KB_NUMS[row]:KB_ROWS[row];
int n=strlen(keys);
int totalW=n*(KB_KW+KB_GAP)-KB_GAP,x0=(DISP_W-totalW)/2;
int y=KB_Y0+row*(KB_KH+KB_GAP);
for(int k=0;k<n;k++){
int x=x0+k*(KB_KW+KB_GAP);
if(kbHit(tx,ty,x,y,KB_KW,KB_KH))return keys[k];
}
}
int y4=KB_Y0+3*(KB_KH+KB_GAP);
if(kbHit(tx,ty,20,y4,80,KB_KH))return'\x03';
if(kbHit(tx,ty,108,y4,164,KB_KH))return' ';
if(kbHit(tx,ty,280,y4,80,KB_KH))return'\x08';
if(kbHit(tx,ty,368,y4,92,KB_KH))return'\x0D';
return 0;
}
// ═══════════════════════════════════════════════════════════
// UI — ПРОГРЕСС (антимерцание)
// ═══════════════════════════════════════════════════════════
static void drawProgressHeader(){
gfx->fillRect(0,0,DISP_W,80,C_BG);
gfx->setTextSize(2);uiText(20,18,"Argon2id (SD)",C_GREEN);
char sub[80];snprintf(sub,sizeof(sub),"t=%u m=%u KB p=%u",A2_T,A2_M,A2_P);
uiText(20,46,sub,C_HINT);
// нарисовать пустую полосу один раз
int bx=20,by=90,bw=DISP_W-40,bh=36;
uiRect(bx,by,bw,bh,C_BAR_BG,C_SEP,4);
}
static void drawProgress(){
uint32_t elMs=millis()-gStartMs;
uint32_t elSec=elMs/1000;
// Прогресс-бар: только дорисовываем новую зелёную часть
int bx=20,by=90,bw=DISP_W-40,bh=36;
int filled=(int)((uint64_t)bw*gDone/TOTAL_FILLS);
if(gDone>0&&filled==0)filled=1; // at least 1px once started
if(filled>gBarFilledPx){
gfx->fillRect(bx+gBarFilledPx,by+1,filled-gBarFilledPx,bh-2,C_BAR_FG);
gBarFilledPx=filled;
}
// Текст обновляем только раз в секунду
if(elSec==gLastElSec)return;
gLastElSec=elSec;
float pct=(gDone>0)?((float)gDone/TOTAL_FILLS*100.0f):0.0f;
uint32_t etaSec=0;
if(gDone>10&&gDone<TOTAL_FILLS)
etaSec=(uint32_t)((float)elMs/gDone*(TOTAL_FILLS-gDone)/1000.0f);
gfx->fillRect(0,136,DISP_W,120,C_BG);
char line[64];
snprintf(line,sizeof(line),"%.1f%% (%lu / %lu)",pct,(unsigned long)gDone,(unsigned long)TOTAL_FILLS);
uiText(20,136,line,C_TEXT,2);
snprintf(line,sizeof(line),"Elapsed: %lus (%lum%lus)",
(unsigned long)elSec,(unsigned long)elSec/60,(unsigned long)elSec%60);
uiText(20,162,line,C_GREEN,2);
if(gDone>10){
if(etaSec>=3600)
snprintf(line,sizeof(line),"ETA: ~%lum%lus",(unsigned long)etaSec/60,(unsigned long)etaSec%60);
else
snprintf(line,sizeof(line),"ETA: ~%lus",(unsigned long)etaSec);
uiText(20,188,line,C_HINT,2);
}
}
// ═══════════════════════════════════════════════════════════
// UI — РЕЗУЛЬТАТ (с прокруткой свайпом)
// ═══════════════════════════════════════════════════════════
// Контент рисуется в виртуальных координатах y_virt.
// На экран попадает только [gScrollY .. gScrollY+RES_VIEW_H).
// Кнопка "Новый" всегда внизу (фиксирована).
#define RLH 20 // высота строки (textSize 2)
#define RLH1 12 // высота строки (textSize 1)
#define RSP 6 // маленький отступ
#define RPX 20 // отступ слева
// Вспомогательный класс — рисует текст только если строка видима
struct RDraw {
int32_t scroll;
int32_t y; // текущая виртуальная Y
void line(const char *s, uint16_t col, uint8_t sz=2){
int sy=(int)y-(int)scroll;
int lh=(sz==1)?RLH1:RLH;
if(sy>=-lh&&sy<RES_VIEW_H){
gfx->setTextSize(sz);gfx->setTextColor(col);gfx->setCursor(RPX,sy);gfx->print(s);
}
y+=lh+2;
}
void sep(){
int sy=(int)y-(int)scroll;
if(sy>=0&&sy<RES_VIEW_H)gfx->drawFastHLine(RPX,sy,DISP_W-2*RPX,C_SEP);
y+=10;
}
void gap(int px=4){y+=px;}
// Для длинных строк (base58): разбить на куски по 38 символов при textSize=2
void longval(const char *s, uint16_t col){
const int CPL=38;
int len=(int)strlen(s);
if(len==0){line("(empty)",C_HINT,2);return;}
for(int off=0;off<len;off+=CPL){
char tmp[42];int take=len-off;if(take>CPL)take=CPL;
memcpy(tmp,s+off,take);tmp[take]='\0';
line(tmp,col,2);
}
}
};
static void drawResultContent(){
// очистить область прокрутки
gfx->fillRect(0,0,DISP_W,RES_VIEW_H,C_BG);
RDraw d;d.scroll=gScrollY;d.y=8;
char buf[96];
// — заголовок —
d.line("RESULT",C_GREEN,2);
snprintf(buf,sizeof(buf),"Login: %s",gLogin);
d.line(buf,C_TEXT,2);
d.gap(4);
d.line("Master secret (base58):",C_HINT,2);
d.longval(gSecretB58,C_GREEN);
snprintf(buf,sizeof(buf),"Time: %lum %lus",
(unsigned long)gElapsedSec/60,(unsigned long)gElapsedSec%60);
d.line(buf,C_HINT,2);
d.sep();
// — 3 ключевые пары —
for(int i=0;i<3;i++){
d.line(KEY_LABELS[i],C_ACCENT,2);
d.line("Pub:",C_HINT,2);
d.longval(gPubB58[i],C_TEXT);
d.line("Priv:",C_HINT,2);
d.longval(gPrivB58[i],C_TEXT);
d.sep();
}
// запомним общую высоту контента для ограничения прокрутки
gScrollMax=(int32_t)d.y - RES_VIEW_H;
if(gScrollMax<0)gScrollMax=0;
}
static void drawResultBtnBar(){
// кнопка "Новый" — фиксирована внизу
gfx->fillRect(0,RES_VIEW_H,DISP_W,RES_BTN_H,C_BG);
uiRect(20,RES_VIEW_H+6,DISP_W-40,RES_BTN_H-12,C_RED,0,6);
gfx->setTextSize(2);gfx->setTextColor(C_TEXT);
gfx->setCursor(DISP_W/2-72,RES_VIEW_H+18);
gfx->print("Generate new");
}
static void drawResultFull(){
gScrollY=0;
drawResultContent();
drawResultBtnBar();
}
// ═══════════════════════════════════════════════════════════
// UI — ДИАЛОГ ПОДТВЕРЖДЕНИЯ
// ═══════════════════════════════════════════════════════════
static void drawConfirmDialog(){
gfx->fillRect(40,120,DISP_W-80,260,C_KEY);
gfx->drawRoundRect(40,120,DISP_W-80,260,8,C_KEY_HL);
gfx->setTextSize(2);gfx->setTextColor(C_TEXT);
gfx->setCursor(60,140);gfx->print("Generate new?");
gfx->setTextSize(1);gfx->setTextColor(C_HINT);
gfx->setCursor(60,172);gfx->print("This will ERASE the saved");
gfx->setCursor(60,184);gfx->print("secret and keys from memory.");
// кнопки
uiRect(60,240,140,56,C_RED,0,6);
gfx->setTextSize(2);gfx->setTextColor(C_TEXT);gfx->setCursor(106,260);gfx->print("Yes");
uiRect(280,240,140,56,C_KEY_HL,C_SEP,6);
gfx->setCursor(316,260);gfx->print("No");
}
// ═══════════════════════════════════════════════════════════
// TOUCH HANDLER
// ═══════════════════════════════════════════════════════════
static void handleTap(int tx,int ty){
if(gState==ST_LOGIN||gState==ST_PASS){
bool isPass=(gState==ST_PASS);
char *buf=isPass?gPass:gLogin;
char c=kbTouch(tx,ty);
if(!c)return;
if(c=='\x03'){gKbNums=!gKbNums;drawInputScreen(isPass?"Password:":"Login:",buf,isPass);return;}
if(c=='\x08'){int n=strlen(buf);if(n>0)buf[n-1]='\0';}
else if(c=='\x0D'){
if(!strlen(buf))return;
if(gState==ST_LOGIN){
for(int i=0;buf[i];i++)if(buf[i]>='A'&&buf[i]<='Z')buf[i]+=32;
gState=ST_PASS;drawInputScreen("Password:",gPass,true);return;
} else {
gState=ST_RUNNING;
gfx->fillScreen(C_BG);drawProgressHeader();
SD_MMC.remove(SD_MEM_FILE);
gSdFile=SD_MMC.open(SD_MEM_FILE,FILE_WRITE);
if(!gSdFile){
gfx->setTextSize(2);gfx->setTextColor(C_RED);
gfx->setCursor(20,200);gfx->println("SD open failed");
gState=ST_ERROR;return;
}
argon2Init(gLogin,gPass);
drawProgress();return;
}
} else {int n=strlen(buf);if(n<63){buf[n]=c;buf[n+1]='\0';}}
drawInputScreen(isPass?"Password:":"Login:",buf,isPass);
return;
}
if(gState==ST_DONE||gState==ST_SAVED){
// Кнопка "Новый" внизу
if(ty>=RES_VIEW_H){
gState=ST_CONFIRM;
drawConfirmDialog();
}
return;
}
if(gState==ST_CONFIRM){
if(tx>=60&&tx<200&&ty>=240&&ty<296){
// Да
nvsClear();
memset(gLogin,0,sizeof(gLogin));memset(gPass,0,sizeof(gPass));
gKbNums=false;gScrollY=0;
gState=ST_LOGIN;
drawInputScreen("Login:",gLogin,false);
} else if(tx>=280&&tx<420&&ty>=240&&ty<296){
// Нет
gState=ST_DONE;
drawResultFull();
}
return;
}
}
// ═══════════════════════════════════════════════════════════
// SETUP
// ═══════════════════════════════════════════════════════════
void setup(){
Serial.begin(115200);
Wire.begin(PIN_I2C_SDA,PIN_I2C_SCL);
// Display
gfx->begin();gBus->writeC8D8(0x36,0xA0);
gfx->setBrightness(200);gfx->fillScreen(C_BG);
// Touch
gTouch.setPins(PIN_TP_INT,-1);
gTouch.begin(Wire,CST92XX_SLAVE_ADDRESS,PIN_I2C_SDA,PIN_I2C_SCL);
gTouch.setMaxCoordinates(DISP_W,DISP_H);
gTouch.setSwapXY(true);gTouch.setMirrorXY(true,false);
attachInterrupt(PIN_TP_INT,onTouchIrq,FALLING);
// SD
gpio_reset_pin(GPIO_NUM_2);
pinMode(PIN_SD_CMD,INPUT_PULLUP);pinMode(PIN_SD_D0,INPUT_PULLUP);
delay(20);
SD_MMC.setPins(PIN_SD_CLK,PIN_SD_CMD,PIN_SD_D0);
bool sdOk=SD_MMC.begin("/sdcard",true);
Serial.printf("SD: %s\n",sdOk?"OK":"FAIL");
if(sdOk)Serial.printf("SD size: %llu MB\n",SD_MMC.cardSize()/(1024ULL*1024));
if(!sdOk){
gfx->setTextSize(2);gfx->setTextColor(C_RED);
gfx->setCursor(20,160);gfx->println("SD CARD ERROR");
gfx->setTextSize(1);gfx->setTextColor(C_HINT);
gfx->setCursor(20,200);gfx->println("FAT32 card required.");
while(true)delay(1000);
}
// PSRAM буферы
gBufPrev=(uint8_t*)ps_malloc(A2_BLKSZ);gBufRef=(uint8_t*)ps_malloc(A2_BLKSZ);
gBufOut=(uint8_t*)ps_malloc(A2_BLKSZ);gBufZero=(uint8_t*)ps_calloc(1,A2_BLKSZ);
gBufAddr=(uint8_t*)ps_malloc(A2_BLKSZ);
gJ1Seg=(uint32_t*)ps_malloc(A2_SEG*sizeof(uint32_t));
if(!gBufPrev||!gBufRef||!gBufOut||!gBufZero||!gBufAddr||!gJ1Seg){
gfx->setTextSize(2);gfx->setTextColor(C_RED);
gfx->setCursor(20,200);gfx->println("PSRAM alloc failed");
while(true)delay(1000);
}
// Попытка загрузить сохранённый результат из NVS
if(nvsLoad()){
gState=ST_SAVED;
drawResultFull();
}else{
drawInputScreen("Login:",gLogin,false);
}
}
// ═══════════════════════════════════════════════════════════
// LOOP
// ═══════════════════════════════════════════════════════════
#define BLOCKS_PER_TICK 4
void loop(){
// ── Touch polling (для свайпа + обычных нажатий) ──────
int16_t tx=0,ty=0;
bool nowTouching=(gTouch.getPoint(&tx,&ty,1)>0);
if(nowTouching&&!gTouching){
// начало касания
gTouchStartY=ty;gTouchPrevY=ty;gTouchCurY=ty;gTouchCurX=tx;
gTouching=true;
} else if(nowTouching&&gTouching){
int16_t delta=gTouchPrevY-ty;
gTouchCurY=ty;gTouchPrevY=ty;gTouchCurX=tx;
// живая прокрутка результата
if((gState==ST_DONE||gState==ST_SAVED)&&abs(delta)>0){
gScrollY+=delta;
if(gScrollY<0)gScrollY=0;
if(gScrollY>gScrollMax)gScrollY=gScrollMax;
drawResultContent(); // перерисовать только контент (без кнопки)
}
} else if(!nowTouching&&gTouching){
// конец касания: если почти не скроллили — считаем нажатием
int16_t swipeDelta=abs(gTouchStartY-gTouchCurY);
if(swipeDelta<15)handleTap(gTouchCurX,gTouchCurY);
gTouching=false;
}
// ── Argon2id вычисление ────────────────────────────────
if(gState==ST_RUNNING&&gInitDone){
bool done=false;
for(int i=0;i<BLOCKS_PER_TICK&&!done;i++)
done=argon2Step();
drawProgress();
if(done){
argon2Finalize();
gSdFile.close();
gState=ST_DONE;
gScrollY=0;
drawResultFull();
}
}
}
@@ -0,0 +1,10 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
// BLAKE2b state
struct B2State {
uint64_t h[8], t[2], f[2];
uint8_t buf[128];
size_t buflen, outlen;
};
@@ -15,9 +15,10 @@ case "${MODE}" in
widgets) SKETCH_DIR="${DEMO_BASE}/examples/05_LVGL_Widgets" ;;
audio) SKETCH_DIR="${DEMO_BASE}/examples/07_ES8311" ;;
simple) SKETCH_DIR="${ROOT_DIR}/simple_av_test" ;;
argon2) SKETCH_DIR="${ROOT_DIR}/argon2_sd_test" ;;
*)
echo "Unknown mode: ${MODE}" >&2
echo "Use one of: hello, widgets, audio, simple" >&2
echo "Use one of: hello, widgets, audio, simple, argon2" >&2
exit 2
;;
esac