最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

將tiny-keccak的核心部分幾乎徹底重構(gòu)為keccak-state的過程

2023-05-28 11:43 作者:進(jìn)棧檢票  | 我要投稿

八千字的流水賬,基本把整個keccak-state的歷程和涉及到的所有東西梳理了一次()有什么沒提到的之后再補充()

連肝了十個小時,終于幾乎徹底重構(gòu)了tiny-keccak的核心部分……這玩意總算被我盤光溜了……

背景介紹:tiny-keccak是Rust社區(qū)的兩大Keccak/SHA3(/(c)SHAKE/K12)實現(xiàn)之一。另外一大是Rust Crypto組織的,他們維護(hù)了全套的密碼學(xué)算法的pure Rust實現(xiàn),并且所有實現(xiàn)都基于一整套完善且優(yōu)化的抽象。但是整一套抽象還是比較重的,對于想要輕依賴的使用者來說并不友好,而tiny-keccak是0依賴的。而且Rust Crypto那套抽象基于typenum和generic array,屬于const泛型還不完善的情況下強迫癥比較受不了的那種權(quán)宜之計(但沒辦法這是現(xiàn)階段大量寫trait必需的)。

我一開始就是基于tiny-keccak實現(xiàn)BCSP協(xié)議,逐漸發(fā)現(xiàn)需要很多tiny-keccak沒提供的工具函數(shù),比如簡化的創(chuàng)建狀態(tài)、輸出到定長數(shù)組、流密碼要用到的XOR輸出、甚至跳過輸出。但是tiny-keccak的內(nèi)部狀態(tài)沒公開,導(dǎo)致實現(xiàn)這些函數(shù)得套兩層娃,雖然inline了沒性能損失,但強迫癥怎么可能受得了。而且作為流密碼使用最好在釋放內(nèi)存之前把內(nèi)部狀態(tài)清零,以防緩沖區(qū)泄漏這樣的事情,而保證清零操作不被編譯器優(yōu)化掉需要用到atomic fence和volatile write,這個Rust Crypto已經(jīng)幫我們提供了一個抽象也就是zeroize庫(不得不說Rust Crypto真的搞了不少基礎(chǔ)設(shè)施,hex-literal也是他們的),但是我們知道不能給別的crate的結(jié)構(gòu)實現(xiàn)trait,這就不得不單開一個crate才能實現(xiàn)了??紤]到核心的內(nèi)部狀態(tài)也就三四百行的規(guī)模,以及這玩意已經(jīng)三年沒發(fā)新版了,我開了一個tiny-keccak-core,把它的內(nèi)部狀態(tài)單獨拿出來公開,然后再在BCSP實現(xiàn)里封裝cSHAKE。

一開始做的改動也就是加上了釋放時zeroize,對API小的調(diào)整,但是我這種強迫癥怎么可能忍得住不左改改右改改。就在這時我把目光放在了一處unsafe代碼,用裸指針寫的XOR,對于現(xiàn)代編譯器優(yōu)化真的有必要嗎?于是我上Rust Users問了一下(https://users.rust-lang.org/t/93119/),得到的結(jié)論是沒必要,改成直接的for或者zip就可以,用for的話可以通過reslicing保證長度一致從而提高性能(這個問題還有后話)。

接下來就一發(fā)不可收拾了。先是看那個宏實現(xiàn)的Keccak函數(shù)不爽,最后直接把宏拆了,只有類型化實現(xiàn)用宏,把函數(shù)本身解放出來。接下來又看向encode len實現(xiàn),覺得可以用ctlz優(yōu)化一下,本來是沒看懂瞎貓碰死耗子式優(yōu)化,后來試著把測試用例寫成hex才看明白,原來就是把長度的長度也做了一個編碼,于是用absorb兩次而不需要中間緩存的方式實現(xiàn)了,(之前還擔(dān)心absorb兩次會不會多出開銷,經(jīng)過后面重構(gòu)fold才完全明白新寫入的數(shù)據(jù)量大于rate才會置換,不過多一次調(diào)用開銷也算開銷),順便直接做成了方法。為了能實現(xiàn)K12的完備性把delim作為了公開字段。

后來到了我在高鐵上把BCSP的握手寫了個大概(基本只是理清了邏輯,還完全沒打磨抽象)的時候,BCSP那邊的cSHAKE封裝已經(jīng)很完善了,除了上面提到的工具函數(shù)之外還有一個類型化的custom string實現(xiàn),方便使用的同時還能保證不同custom string的實例不放錯地方(這算是Rust中除了const泛型之外另一種實現(xiàn)dependent type的方法)。于是沒怎么猶豫就把這套cSHAKE實現(xiàn)搬到了此時已經(jīng)改名為keccak-core的倉庫,BCSP那邊只留一個實現(xiàn)custom string的宏調(diào)用。

決定讓BCSP走在無TLS并且默認(rèn)無HTTP握手的WebSocket上之后,看向21年寫LiveKit的時候就已經(jīng)很熟悉的tungstenite庫,發(fā)現(xiàn)依賴了一個rand庫來實現(xiàn)生成隨機mask(實際上還有HTTP握手用的隨機字符串)。實際上mask是為了防止跟HTTP請求混淆,對于無HTTP握手的情況,mask是沒有用的,但畢竟是WebSocket的RFC里面的東西,config里加個開關(guān)還好,怎么可能給你加個feature把rand依賴關(guān)掉。而rand本身不小不說,里面的默認(rèn)算法是ChaCha12,造BCSP就是為了做少依賴實驗,能被Keccak替代的算法我怎么可能忍住讓它出現(xiàn)在協(xié)議里。于是我想到了我還沒把25519研究明白(現(xiàn)在至少明白到能自己基于base庫實現(xiàn)密鑰交換和簽名了,盡管除了我自己想出來的基于交換的簽名之外,ed25519風(fēng)格的簽名還沒完全弄明白,之后可能單獨講講25519的事,預(yù)告一下這玩意還有個448bit的版本)的時候看ed25519庫如何強行替換掉SHA2-512算法的方式(https://github.com/dalek-cryptography/ed25519-dalek/issues/64),就是貍貓換太子,用cargo的patch放一個自己版本的同名包,然后里面寫上自己的實現(xiàn),只要調(diào)用它的庫調(diào)用的方式是固定的就沒問題。于是就得學(xué)著rand庫實現(xiàn)一個thread local的狀態(tài),它直接用Rc<UnsafeCell<State>>來繞開的LocalKey只能提供不可變引用,中間害怕自己實現(xiàn)的不sound還去問了問(https://users.rust-lang.org/t/94278)(確實是sound的,只要封裝起來不要允許直接訪問引用就沒問題,LocalKey不給可變引用是怕同時拿兩個可變引用)。實現(xiàn)的時候順便抽象出了Absorb Squeeze Once三個trait,把工具函數(shù)全封到了trait里,然后控制thread local狀態(tài)只能squeeze。

實現(xiàn)完了rand之后,把getrandom的輸入換成了可選的MaybeUninit,然后就想squeeze到定長數(shù)組這些是不是也能改成直接寫到未初始化,過程中了解了一些關(guān)于未初始化內(nèi)存的知識(內(nèi)存是知道自己有沒有被初始化的,而如果沒有,那編譯器想怎么來就怎么來,并不一定是指向一個固定的隨機內(nèi)存)(https://www.ralfj.de/blog/2019/07/14/uninit.html),看了一些資料和以往提問后直覺覺得不太可能sound的實現(xiàn)出來,結(jié)果問了一下確實(https://users.rust-lang.org/t/94321),而且以0初始化的情況下會有一個底層指令去找zeroed的未分配內(nèi)存,所以實際上一般也沒這個寫開銷。這個時候突然注意到一點:

(以上是25號上午10點多肝完之后寫的,我現(xiàn)在一般三四點最晚六點睡所以對我來說算熬夜了,寫到最關(guān)鍵的地方就倒下了,幸虧沒把作息打亂了,然后以下是這兩天斷斷續(xù)續(xù)寫完的(主要是經(jīng)常打斷),盡可能靠筆記和錄屏回憶起來比較完整的思考過程,親身體驗查資料寫代碼的時候做筆記和錄屏都是好習(xí)慣,實際上很多描述都是基于最后完全弄明白之后的理解寫的,前面一點的步驟當(dāng)時可能還沒理解那么清楚)

實際上squeeze的過程就是分很多次把數(shù)據(jù)從內(nèi)部狀態(tài)里用copy_from_slice復(fù)制出來(后面會把本質(zhì)講得更清楚),那么只要把這個復(fù)制換成XOR,不就可以在完全不需要分配的情況下實現(xiàn)XOR輸出了嗎?換成什么都不做不就能實現(xiàn)跳過輸出了嗎?理論上甚至可以這樣實現(xiàn)從一個內(nèi)部狀態(tài)squeeze到另一個內(nèi)部狀態(tài),這是很有用的,比如一個狀態(tài)生成密鑰,生成的密鑰喂給加解密和MAC狀態(tài),BCSP里就有這樣的結(jié)構(gòu),不過實現(xiàn)會比較復(fù)雜而且容易bug,所以到現(xiàn)在也還沒實現(xiàn)。于是我基于他原本的Buffer接口,參考xorin和setout寫了xorout和skipout,并且把state中absorb和squeeze的過程通過宏抽象出來,在調(diào)用宏時指定調(diào)用的Buffer操作方法,然后基于宏實現(xiàn)absorb squeeze squeeze_xor squeeze_skip方法。但原本的Buffer接口這么改有一個小問題,skipout實際上不做任何操作,所以傳進(jìn)去的也是一個空的slice,但是在squeeze過程中還是會reslice這個slice,而reslice空的slice不用說肯定panic。于是我做了一個小改動,把輸入/輸出的offset也傳進(jìn)xorin/{set, xor, skip}out這些方法里,然后其余的在里面reslicing,skipout什么都不做。而對于cSHAKE抽象,則把Squeeze trait拆分成Squeeze SqueezeXor SqueezeSkip,刪除基于squeeze申請新內(nèi)存的實現(xiàn),改為直接調(diào)用state的squeeze_xor和squeeze_skip方法。

其實他這個Buffer接口是我一直看著不順眼。Keccak的內(nèi)部緩沖區(qū)需要通過兩種方式讀寫,一種是以64位為一個單位(word)的Keccak置換(實際上是對于1600是64位一個word,對于800是32位一個word,對于最小的25甚至是1位一個word),另一種是以字節(jié)為單位的輸入和輸出。而這里面就涉及到一個大端序和小端序的問題,因為大端序和小端序的數(shù)字在內(nèi)存中排布不一樣,所以就導(dǎo)致大端序機和小端序機做相同的置換,然后以字節(jié)形式輸出的內(nèi)容是不同的。解決這個問題的辦法是每次涉及切換不同類型的操作之前和之后swap一下,而內(nèi)部狀態(tài)本身順理成章的是跟Keccak置換一致的u64數(shù)組,所以他們在發(fā)現(xiàn)這個問題之后,順理成章的選擇了封裝出一個Buffer,然后把所有對字節(jié)的操作封裝成execute,接收要操作字節(jié)段的offset和len以及操作的閉包(原本也就是XOR輸入和復(fù)制輸出,我加上了XOR輸出和什么都不做),然后在小端機器上直接調(diào)用函數(shù)操作,小端機器上在字節(jié)操作之前和之后把字節(jié)操作涉及到的那些u64 swap一次,這樣實現(xiàn)了把大端小端機的差異做了封裝,很順理成章,但是總感覺有點難受,尤其是pad操作只動兩個字節(jié)還要調(diào)用兩次execute。

而Rust Crypto那邊的SHA3/(c)SHAKE實現(xiàn)我之前一直沒怎么細(xì)看,只是在重實現(xiàn)encode len的時候看了一下(實際上encode len的實現(xiàn)都沒區(qū)別,C的XKCP那邊也是那么實現(xiàn)的,我的方法雖然直接但是算是新方法),只知道似乎是通過只接收長度已經(jīng)是8的倍數(shù)的輸入/輸出實現(xiàn)的,相當(dāng)于把byte操作也轉(zhuǎn)換成了word操作,所以不需要swap endian,但是一直沒看懂具體怎么實現(xiàn)的。為了解決Buffer的問題(也不能說是問題,就是看著不順眼),我就想一定要研究明白,還展開了宏方便跳轉(zhuǎn)。結(jié)果這次注意到Rust Crypto的實現(xiàn)似乎好簡單啊,內(nèi)部狀態(tài)就u64數(shù)組的buffer、RC和delim,而RC實際上完全沒用到。(這同時也引起了我對哪些內(nèi)部狀態(tài)可以提取成const泛型的思考。)而tiny-keccak的版本有buffer、offset(對啊為什么Rust Crypto沒有buffer的offset呢)、rate、delim、mode(這個Rust Crypto是單獨的squeeze reader所以不需要區(qū)分)、還有一個不占大小的phantom type用來放(類似于我cSHAKE實現(xiàn)中custom string的)Keccak-f和Keccak-p的類型化(實際上這個也能替換成const泛型……這么一說的話custom string也可以誒,(正在寫這篇文章的時候)試了一下效果還不錯,但就是目前還不支持&'static作為const泛型所以暫時是不可以)。

于是我開始仔細(xì)看Rust Crypto的實現(xiàn)。首先發(fā)現(xiàn)它的內(nèi)部狀態(tài)absorb進(jìn)去的都是固定長度的block,而這個block的長度是……136?似乎沒見過這個數(shù)字,然而很快就反應(yīng)過來這是bits_to_rate(256)。為了generic array直接把rate硬編碼了,也沒有任何注釋。也就是每次處理一block的輸入,這一block也就是一個rate。聯(lián)想到rate是跟安全參數(shù)掛鉤的參數(shù),安全參數(shù)的bits越大rate就越小,rate越小相當(dāng)于進(jìn)行置換的輸入間隔就越小,安全系數(shù)越高就越需要頻繁的置換,嗯,合理。而Rust Crypto的抽象直接就是update blocks,那么它是怎么處理任意長度的輸入的呢?很快繞進(jìn)了抽象的迷宮里,算了還是繼續(xù)回去改吧。

抽離出absorb和squeeze的過程之后,我發(fā)現(xiàn)它們的邏輯幾乎完全一致,并且absorb標(biāo)注是first foldp,squeeze標(biāo)注是second foldp,foldp大概是標(biāo)準(zhǔn)文檔里的命名吧。于是我很快將兩個宏合并為一個foldp。下一步開始處理Buffer,首先把Buffer的方法全部轉(zhuǎn)移到直接在狀態(tài)上,以方便之后慢慢合并。在去除Buffer的過程中我開始好奇是什么時候把buffer這玩意引入的,就在tiny-keccak原倉庫blame了一下,結(jié)果發(fā)現(xiàn)就是在發(fā)現(xiàn)大小端問題之后加的(前面細(xì)說過了)。還有一個額外發(fā)現(xiàn):之前那個裸指針寫的XOR是來自一個PR,最早的代碼就是很直接的zip,然而那個PR據(jù)當(dāng)時的作者們說性能提升了35%-50%!不過那個PR是7年前的事了。立即去前面提到的帖子里繼續(xù)問,是不是編譯器在這7年間在這方面有更多的優(yōu)化,結(jié)果得到了肯定的答復(fù),zip的性能大幅度優(yōu)化了,現(xiàn)在用帶reslicing的for和zip都可以。

回去接著看Rust Crypto的實現(xiàn)。實際上Rust Crypto實現(xiàn)的Keccak置換函數(shù)是一個單獨的crate,也沒有那些復(fù)雜的抽象,而且他們通過抽象出LaneSize也就是前面提到的每個word的長度,實現(xiàn)了從u8對應(yīng)的200到u64對應(yīng)的1600的不同容量的Keccak函數(shù),而且還提供了ARM的Keccak指令集上的實現(xiàn)(沒錯目前唯一實現(xiàn)了硬件加速Keccak/SHA3的主流處理器架構(gòu)是ARM,Intel你給勁啊,AMD你得好好勸勸?。约热灰呀?jīng)有完善的能滿足需求的實現(xiàn),置換函數(shù)之后就打算用他們的實現(xiàn)了,這也是最后選擇改名為keccak-state的原因,相當(dāng)于在置換函數(shù)實現(xiàn)的基礎(chǔ)上實現(xiàn)了一個內(nèi)部狀態(tài)。另外那個時候才注意到Keccak-p的RC其實就是Keccak-f的后12輪的RC,之前一直沒有注意到。

接下來我準(zhǔn)備從頭開始,對照著兩家的實現(xiàn)來重新實現(xiàn)。一開始打算把LaneSize也作為泛型參數(shù),從而直接使得這個內(nèi)部狀態(tài)能作為從200到1600各種容量的Keccak函數(shù)的內(nèi)部狀態(tài),但后面發(fā)現(xiàn)zeroize等很多地方麻煩比較多,就放棄了,還是只做實際使用的1600。rate和delim當(dāng)時想著都可以作為const泛型。

簡單搭好架子之后,接著看Rust Crypto的那套抽象是怎么處理任意長度輸入的。結(jié)果發(fā)現(xiàn)包裝器里其實還有個Buffer,這個Buffer里除了有一個block大小的的數(shù)組以外還有一個offset,這下就知道offset去哪了……而且他那個offset還是u8的,雖然block長度小于256,但其實沒什么意義,反正每次用的時候都得轉(zhuǎn)成usize,看似省了空間,實際上每次都得還原回同樣多的空間,還多了個轉(zhuǎn)換的開銷呢。然后定位到一個叫digest block的方法,接收一個任意長度的輸入和一個接收若干block去改變(具體算法)內(nèi)部狀態(tài)的閉包。實際上這個東西就基本上對應(yīng)tiny-keccak那邊的foldp了,只不過那邊沒有單獨的block buffer。里面的邏輯大概就是把輸入的長度和block長度和offset分了幾種情況討論,比如輸入的長度還不到當(dāng)前buffer剩余的長度,那就只復(fù)制進(jìn)來不調(diào)用改變內(nèi)部狀態(tài),再比如輸入比block長,那就用chunk分為和block等長的好幾段,再逐個調(diào)用改變內(nèi)部狀態(tài),然后有還剩下的就再放到buffer里面,等下次更多數(shù)據(jù)來了一塊進(jìn)去。這個Buffer的實現(xiàn)似乎還采取了一些精巧的手段來給編譯器更多的信息,來防止生成panic path,總之設(shè)計都很精致。而swap endian的問題,除了輸入輸出都是一block長之外(輸出也是差不多的過程),實際上是輸入輸出都是直接用8個字節(jié)chunk,然后輸入的時候每8個字節(jié)from_le_bytes,輸出的時候每8個字節(jié)to_le_bytes,在這個過程中自然地完成轉(zhuǎn)換,跟內(nèi)部狀態(tài)直接接觸的都是正確endian的u64。

在完全弄明白這些之后,覺得其實那堆抽象也就是那么回事,而且不用怎么想就能知道這中間引入了多少開銷:在內(nèi)部狀態(tài)之外多了一個block buffer的開銷,在內(nèi)部狀態(tài)和block buffer之間相互復(fù)制的開銷,轉(zhuǎn)換u64可能的復(fù)制開銷……還是簡單直接的tiny-keccak更適合我。不過它的處理方式引起了我的思考:內(nèi)部狀態(tài)必須是u64嗎?必須是字節(jié)操作的時候transmute再swap endian嗎?能不能反過來,內(nèi)部狀態(tài)本身是字節(jié),字節(jié)操作的時候就直接來,而置換操作的時候transmute再swap endian?因為字節(jié)操作很多時候只操作一部分甚至只操作一個字節(jié),所以就得選擇一部分被操作了的swap endian,而置換操作的時候一定是操作整個狀態(tài),所以整個swap就可以。為什么已經(jīng)決定把字節(jié)操作和置換操作放一起了,還要讓碎片化、頻繁的字節(jié)操作遷就整塊的置換操作?搞得pad兩個字節(jié)也得調(diào)用一下execute,對于大端機swap四次?雖然幾乎沒有大端機,但是這河里嗎?這一點都不河里。

于是放棄之前寫了個開頭的從零開始,繼續(xù)拆execute。為了方便稱呼內(nèi)部狀態(tài)的buf和外部輸入輸出的buf,將外部輸入輸出的buf統(tǒng)一稱為iobuf,然后統(tǒng)一一下absorb squeeze squeeze_xor squeeze_skip幾個方法對iobuf reslicing的過程。接下來就是把內(nèi)部狀態(tài)的buf改成字節(jié)數(shù)組,然后在把transmute和swap endian這些都放進(jìn)置換方法里,execute只保留最簡單的reslicing和調(diào)用。

接下來先做的改動是把Keccak-f和Keccak-p的類型化刪掉,改成const泛型。由于const泛型不支持自定義類型,所以只能放一個bool,用true代表Keccak-f,false代表Keccak-p,并且寫好常量,真是C-style啊,不過比多一個phantom data舒服。下一步繼續(xù)加const泛型,把rate也作為const泛型,能給每個內(nèi)部狀態(tài)省8byte的空間呢。相同的辦法,定義一堆R128 R224 R256 R384 R512的常量來裝這些rate值。接下來想把delim也作為const泛型,但是問題有點多,因為在很多算法里delim是變化的,比如cSHAKE在name和custom string都為空的時候就是SHAKE,用的是SHAKE的delim,K12更是有好幾種delim,再加上delim就一個字節(jié),所以還是算了。不過還是把以前直接把delim公開的方式優(yōu)化了一下,改成了change delim方法返回重新構(gòu)建的改了delim的內(nèi)部狀態(tài)。

下一步就是拆除只剩下reslicing和調(diào)用的execute了,把reslicing拆到xorin/{set, xor, skip}out幾個方法里,這樣它們就是整齊的對buf和iobuf reslicing然后調(diào)用接收一個&mut [u8]一個&[u8]的方法了,pad也終于恢復(fù)了只是XOR兩個字節(jié)的方式。

接下來就是重量級中的重量級了,把foldp抽象為一個方法。最后選擇的接口形式是,傳入一個要輸入/輸出字節(jié)(iobuf)的長度,和一個用于操作iobuf的閉包,這個閉包會傳入整個內(nèi)部狀態(tài)的buf、內(nèi)部狀態(tài)的offset、iobuf的offset和本次操作的len,然后在這個閉包里面reslicing然后操作。然后基于foldp傳入xor(buf, iobuf)的操作函數(shù)就是absorb,傳入copy(iobuf, buf)就是squeeze,傳入xor(iobuf, buf)就是squeeze_xor,傳入什么也不做就是squeeze_skip。接口上有點像原先的execute,但之前的是閉包帶著內(nèi)部狀態(tài)buf操作iobuf,現(xiàn)在的是閉包帶著iobuf操作內(nèi)部狀態(tài)buf,剛好反過來。這樣能帶來兩個好處,一個是解決了輸入是不可變引用、輸出是可變引用、skip甚至是沒有引用的差異問題,把iobuf帶在閉包里而不是直接傳進(jìn)去,使得給幾個輸入輸出方法提供統(tǒng)一的foldp接口成為可能,特別是對于skip,不僅沒有不能reslicing一個空的slice的問題,而且連內(nèi)部狀態(tài)buf都不需要reslicing了(雖然應(yīng)該會優(yōu)化掉);另一個是對于for實現(xiàn)的XOR來說,reslicing就在旁邊,而不是分散在兩個函數(shù)離得八丈遠(yuǎn),不需要單獨抽象出來的函數(shù)來做重新告訴編譯器這倆一樣長的工作。極度舒適,沒有一行冗余代碼,既不需要一個u64單位的內(nèi)部狀態(tài)跟一個字節(jié)單位的block buffer然后之間復(fù)制來復(fù)制去,也不需要只有一個u64的內(nèi)部狀態(tài)然后每次字節(jié)操作哪怕只是動一個字節(jié)都得swap一遍endian,真正符合了duplex清潔高效的特點。強迫癥得到完全滿足,即使當(dāng)時已經(jīng)肝了十個小時也倍感欣慰。另外在改出這個新的foldp抽象的過程中,我也把原本簡寫的變量名全部改成最明確的,改完了就發(fā)現(xiàn)自己大概看懂了。實際上跟Rust Crypto那邊的absorb block做的事情基本上是一樣的,相當(dāng)于也是把輸入的長度和block長度和offset分了幾種情況討論,只不過純用幾個變量做的判斷,帶了幾個數(shù)字試了試,邏輯是沒有問題的。

另外在完全理解這個過程(應(yīng)該對應(yīng)sponge-duplex的duplex部分,畢竟符合沒有輸出轉(zhuǎn)換的特點)之后,其實有個很好的比喻方式。整個過程就像是種地,xorin就是把輸入數(shù)據(jù)播種到內(nèi)部狀態(tài),Keccak置換就是耕地,{set, xor, skip}不管哪種out就是從內(nèi)部狀態(tài)收獲輸出數(shù)據(jù)。播種的長度是任意的,收獲的長度也是任意的,反正種/收夠了rate那么多就翻一次土。要求的安全級別越高,翻土就越頻繁。

下一步執(zhí)行之前做的決定,換成Rust Crypto的Keccak置換函數(shù)實現(xiàn),順便把字節(jié)形式和u64形式的元素數(shù)量都通過容量bits+const函數(shù)定義了,這樣方便未來直接通過LaneSize或者const泛型選擇容量。

這個時候已經(jīng)沒什么要改的了,項目改名為keccak-state,調(diào)整一下項目結(jié)構(gòu)的遺留問題,當(dāng)天就去寫這篇的前半部分內(nèi)容了。

然后是今天接著寫的時候提到custom string能不能用const泛型,試了一下不行,然后寫開了就接著寫,先實現(xiàn)了cSHAKE狀態(tài)的reset。cSHAKE是有rate、name、custom string這幾個初始狀態(tài)的,所以reset直接把內(nèi)部狀態(tài)buf清空是不行的。Rust Crypto那邊的實現(xiàn)方式是多一個reset feature,開啟的時候在內(nèi)部狀態(tài)里面加一個填了幾個初始狀態(tài)之后的狀態(tài),reset的時候復(fù)制一份。我的cSHAKE實現(xiàn)不需要這樣做,因為custom string是類型化實現(xiàn),所以隨時能訪問到,reset的時候再填一遍初始狀態(tài)就好了。然后由reset想到reseed,前面說到我學(xué)著rand庫做了一個thread local的用于生成隨機數(shù)據(jù)的狀態(tài),而rand庫的實現(xiàn)是會自動reseed的,每輸出了指定數(shù)量的字節(jié)就從系統(tǒng)重新獲取一次熵。于是我也寫了一個簡單的reseedable狀態(tài),在cSHAKE狀態(tài)的基礎(chǔ)上加一個remain,每次squeeze完了檢查一下有沒有用完。另外在通用的cSHAKE的狀態(tài)上也加了一個通過feature開啟的seed方法。很快就想到一個問題:萬一一次要的字節(jié)數(shù)量就超過了reseed間隔怎么辦?先不說怎么辦,減掉之后remain就成負(fù)的了。嘗試思考了一會之后,我突然意識到實際上這是跟foldp類似的問題,輸入/輸出的長度和內(nèi)部狀態(tài)的長度和offset可能存在各種情況,只不過是rate換成了reseed間隔,執(zhí)行置換變成了執(zhí)行reseed。于是我把foldp直接套上去改了一下,結(jié)果帶數(shù)字看邏輯完全沒問題。感覺或許有必要把foldp抽象成單獨的邏輯了,就跟Rust Crypto的block buffer抽象一樣,但不需要額外的buffer。另外也調(diào)整了一下foldp的過程,他原本的foldp把buf_offset弄了一個內(nèi)部變量,實際上直接用內(nèi)部狀態(tài)的字段self.offset就可以。每次不管是置換還是reseed都一定會伴隨著offset的歸零,所以我就直接把offset歸零放到置換/reseed方法里了。另外還有一個涉及到置換和offset歸零的操作,就是cSHAKE填完初始狀態(tài)之后的fill_block,實際上現(xiàn)在也好理解了,就是無論你offset在哪都置換一次并且offset歸零,相當(dāng)于多輸入了這個rate長度(也就是這個block)剩下的長度個0(XOR 0等于沒變),也就是把當(dāng)前這個rate長度(這個block)“填滿了”(0)。

然后就又由reset的問題想到之前就想過的預(yù)計算初始狀態(tài),因為正常情況下一個系統(tǒng)(比如說BCSP)有很多cSHAKE custom,每個新的custom狀態(tài)都需要absorb一遍相同的初始狀態(tài),對于要初始化很多狀態(tài)的系統(tǒng)來說,這無疑是不容忽視的性能開銷。于是實際上可以在custom的類型化trait中加一個常量,來放置這個custom的初始狀態(tài),每次創(chuàng)建或者reset的時候都直接復(fù)制這個初始狀態(tài)就可以。然后這個初始狀態(tài)可以用構(gòu)建腳本或者過程宏計算,const上下文和macro rules是肯定不能做這么復(fù)雜的計算的,之后還得寫一個過程宏。我也試了一下生成出來粘貼進(jìn)代碼里,結(jié)論是基本不可行,每個custom 200字節(jié)呢,放在源碼里還是比較礙眼的。添加了內(nèi)部狀態(tài)的從初始狀態(tài)生成的方法和輸出內(nèi)部狀態(tài)的方法(生成的時候用)。順便補充了一下custom string的類型化實現(xiàn),補上了可選的name,增加了直接判斷是否兩者都為空和直接返回delim的方便的函數(shù)。

(其實這后半部分大部分也是(對我來說的)熬到上午寫的,前面要么各種事打斷要么寫不出來,等到7點鐘才進(jìn)入狀態(tài),然后寫到現(xiàn)在11點,希望作息能跟三天前一樣不亂掉吧,主要是不寫完心里就會一直有這件事,沒法開別的工,所以只好憋一口氣無論如何寫完)

將tiny-keccak的核心部分幾乎徹底重構(gòu)為keccak-state的過程的評論 (共 條)

分享到微博請遵守國家法律
兖州市| 扎赉特旗| 伊金霍洛旗| 西平县| 徐水县| 兴安盟| 娄烦县| 隆化县| 玛曲县| 理塘县| 巢湖市| 河北区| 宜川县| 日喀则市| 达日县| 大足县| 金昌市| 昌黎县| 商南县| 新巴尔虎左旗| 进贤县| 额敏县| 四子王旗| 溧阳市| 钟山县| 无极县| 株洲市| 收藏| 麻栗坡县| 稻城县| 湄潭县| 晋中市| 玉山县| 南充市| 中西区| 集安市| 清新县| 务川| 大同市| 泾川县| 连云港市|