From ddc0f00d41a34cb41d675fb7e53c039b5c570a6f Mon Sep 17 00:00:00 2001 From: Balazs Komuves Date: Tue, 21 Jan 2025 14:55:59 +0100 Subject: [PATCH] add sponge for u64 inputs --- reference/src/Sponge.hs | 90 ++++++++++++++---- reference/src/U64.hs | 42 +++++++++ src/dom_sep.rs | 46 +++++++++ src/lib.rs | 4 + src/sponge.rs | 201 +++++++++++++++++++++++++++++++--------- src/words.rs | 67 ++++++++++++++ 6 files changed, 387 insertions(+), 63 deletions(-) create mode 100644 reference/src/U64.hs create mode 100644 src/dom_sep.rs create mode 100644 src/words.rs diff --git a/reference/src/Sponge.hs b/reference/src/Sponge.hs index 6d90ecb..b50cc29 100644 --- a/reference/src/Sponge.hs +++ b/reference/src/Sponge.hs @@ -4,16 +4,20 @@ module Sponge where -------------------------------------------------------------------------------- +import Data.Word + import BN254 import Permutation +import U64 -------------------------------------------------------------------------------- data Config = Config - { rate :: Int -- ^ rate of the sponge - , width :: Int -- ^ width of the permutation - , nbits :: Int -- ^ number of bits in the input elements (eg: 1,8,64,254) - , padding :: Int -- ^ padding strategy (255 => no padding; 1 => @10*@ padding) + { rate :: Int -- ^ rate of the sponge + , width :: Int -- ^ width of the permutation + , nbits :: Int -- ^ number of bits in the input elements (eg: 1,8,64,254) + , padding :: Int -- ^ padding strategy (255 => no padding; 1 => @10*@ padding) + , mblen :: Maybe Int -- ^ optional length (makes no padding safe, at least for non-empty inputs) } deriving Show @@ -24,36 +28,41 @@ domainSep (Config{..}) + fromIntegral width * (2^8 ) + fromIntegral nbits * (2^16) + fromIntegral padding * (2^24) - + fromInteger (2^64) + + fromInteger 1 * (2^64) + + fromIntegral len * (2^72) + where + len = case mblen of + Nothing -> 0 + Just l -> l -------------------------------------------------------------------------------- --- | Sponge construction with rate=2 (capacity=1), zero IV and 10* padding +-- | Sponge construction with rate=2 (capacity=1)), and 10* padding feltSpongeWithPad :: [F] -> F -feltSpongeWithPad input = go (0,0,civ) (pad_10star input) where +feltSpongeWithPad input = go (0,0,civ) (felts_pad_10star input) where -- domain separation capacity IV - civ = domainSep $ Config { rate = 2 , width = 3 , nbits = 254 , padding = 1 } + civ = domainSep $ Config { rate = 2 , width = 3 , nbits = 254 , padding = 1 , mblen = Nothing } go (sx,_ ,_ ) [] = sx go (sx,sy,sz) (a:b:rest) = go state' rest where state' = permute (sx+a, sy+b, sz) -pad_10star :: [F] -> [F] -pad_10star = go where - go (x:y:rest) = x : y : go rest - go [x] = [x,1] - go [] = [1,0] + felts_pad_10star :: [F] -> [F] + felts_pad_10star = go where + go (x:y:rest) = x : y : go rest + go [x] = [x,1] + go [] = [1,0] -------------------------------------------------------------------------------- --- | Sponge construction with rate=2 (capacity=1), zero IV and no padding -feltSpongeWithoutPad :: [F] -> F -feltSpongeWithoutPad input - | input == [] = error "the combination of empty input and no padding is not allowed!" - | otherwise = fst3 $ go (0,0,civ) input where +-- | Sponge construction with rate=2 (capacity=1), and no padding +feltSpongeWithNoPad :: [F] -> F +feltSpongeWithNoPad input + | null input = error "the combination of empty input and no padding is not allowed!" + | otherwise = fst3 $ go (0,0,civ) input where -- domain separation capacity IV - civ = domainSep $ Config { rate = 2 , width = 3 , nbits = 254 , padding = 255 } + civ = domainSep $ Config { rate = 2 , width = 3 , nbits = 254 , padding = 255 , mblen = Just (length input) } add (x,y) (sx,sy,sz) = (sx+x, sy+y, sz) @@ -62,3 +71,46 @@ feltSpongeWithoutPad input go state (a:b:rest) = go (permute $ add (a,b) state) rest -------------------------------------------------------------------------------- + +-- | Sponge construction for 64 bit words with rate=2 (capacity=1), and 10* padding +u64SpongeWithPad :: [Word64] -> F +u64SpongeWithPad input = go (0,0,civ) (chunk_pad_10star input) where + -- domain separation capacity IV + civ = domainSep $ Config { rate = 2 , width = 3 , nbits = 64 , padding = 1 , mblen = Nothing } + + go (sx,_ ,_ ) [] = sx + go (sx,sy,sz) (this:rest) = go state' rest where + (a,b) = u64sToFelts this + state' = permute (sx+a, sy+b, sz) + + chunk_pad_10star :: [Word64] -> [[Word64]] + chunk_pad_10star = go where + go ws = case splitAt 7 ws of + (this,rest) -> if length this == 7 + then this : go rest + else [take 7 $ this ++ [1] ++ repeat 0] + +-------------------------------------------------------------------------------- + +-- | Sponge construction for 64 bit words with rate=2 (capacity=1), no padding +u64SpongeWithNoPad :: [Word64] -> F +u64SpongeWithNoPad input + | null input = error "the combination of empty input and no padding is not allowed!" + | otherwise = go (0,0,civ) (chunk_nopad input) + where + -- domain separation capacity IV + civ = domainSep $ Config { rate = 2 , width = 3 , nbits = 64 , padding = 255 , mblen = Just (length input) } + + go (sx,_ ,_ ) [] = sx + go (sx,sy,sz) (this:rest) = go state' rest where + (a,b) = u64sToFelts this + state' = permute (sx+a, sy+b, sz) + +chunk_nopad :: [Word64] -> [[Word64]] +chunk_nopad = go where + go ws = case splitAt 7 ws of + (this,rest) -> case rest of + [] -> [take 7 $ this ++ repeat 0] + _ -> this : go rest + +-------------------------------------------------------------------------------- diff --git a/reference/src/U64.hs b/reference/src/U64.hs new file mode 100644 index 0000000..da71181 --- /dev/null +++ b/reference/src/U64.hs @@ -0,0 +1,42 @@ + +{-# LANGUAGE NumericUnderscores #-} +module U64 where + +-------------------------------------------------------------------------------- + +import Data.Bits +import Data.Word + +import BN254 + +-------------------------------------------------------------------------------- + +u64sToFelts :: [Word64] -> (F,F) +u64sToFelts ws = case ws of + [w0,w1,w2,w3,w4,w5,w6] -> (x,y) where + lo = w6 .&. 0xFFFF_FFFF + hi = shiftR w6 32 + x = fromIntegral w0 + + fromIntegral w1 * (2^64) + + fromIntegral w2 * (2^128) + + fromIntegral lo * (2^192) + y = fromIntegral w3 + + fromIntegral w4 * (2^64) + + fromIntegral w5 * (2^128) + + fromIntegral hi * (2^192) + _ -> error "u64s_to_field: expecting 7 words" + +-------------------------------------------------------------------------------- + +exampleU64s :: [Word64] +exampleU64s = + [ 0x_1234_5678_9abc_def0 + , 0x_dead_cafe_9876_0123 + , 0x_fc24_78a3_9d06_8818 + , 0x_365d_9035_1967_7d70 + , 0x_4acb_9086_8bb1_b970 + , 0x_df46_f82e_1f13_59d0 + , 0x_3dbb_3705_9b6e_b13f + ] + +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/dom_sep.rs b/src/dom_sep.rs new file mode 100644 index 0000000..d4bb601 --- /dev/null +++ b/src/dom_sep.rs @@ -0,0 +1,46 @@ + + +// +// domain separation. +// +// we use either +// +// IV := 2^64 + 2^24*padding + 2^16*nbits + 2^8*t + rate +// IV' := IV + 2^72*input_len +// +// in the capacity position, so the inital state is `(0,0,IV)` +// +// where `nbits` is the number of the bits in the input type: +// u = 1 -> bit sequence +// u = 8 -> byte sequence +// u = 64 -> u64 sequence +// u = 254 -> BN254 scalar field element sequence +// +// and padding encodes the padding strategy: +// +// p = 0 -> no padding (only safe with constant length inputs!) +// p = 1 -> `10*` padding strategy +// + +use ark_bn254::Fr as F; + +//------------------------------------------------------------------------------ + +pub fn domain_separator(rate: usize, state_size: usize, input_bits: usize, padding: usize, mb_len: Option) -> F { + let low: u128 = ( rate + (state_size<<8) + (input_bits<<16) + (padding<<24) ) as u128; + let domsep = match mb_len { + None => { + let high: u128 = 1 << 64; + F::from(high + low) + } + Some(len) => { + let high: u128 = (1 + (len<<8) as u128) << 64; + F::from(high + low) + } + }; + println!("domsep = {:?}", domsep ); + domsep +} + +//------------------------------------------------------------------------------ + diff --git a/src/lib.rs b/src/lib.rs index b257513..94450a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ + mod constants; +mod dom_sep; +mod words; + pub mod permutation; pub mod sponge; diff --git a/src/sponge.rs b/src/sponge.rs index 08f962e..556b446 100644 --- a/src/sponge.rs +++ b/src/sponge.rs @@ -1,38 +1,10 @@ -use ark_ff::prelude::{Zero,One}; +use ark_ff::{One, Zero}; use ark_bn254::Fr as F; use crate::permutation::*; - -//------------------------------------------------------------------------------ - -// -// domain separation. -// we use -// -// IV := 2^64 + 2^24*padding + 2^16*nbits + 2^8*t + rate -// -// in the capacity position, so the inital state is `(0,0,IV)` -// -// where `nbits` is the number of the bits in the input type: -// u = 1 -> bit sequence -// u = 8 -> byte sequence -// u = 64 -> u64 sequence -// u = 254 -> BN254 scalar field element sequence -// -// and padding encodes the padding strategy: -// -// p = 0 -> no padding (only safe with constant length inputs!) -// p = 1 -> `10*` padding strategy -// - -fn domain_separator(rate: usize, state_size: usize, input_bits: usize, padding: usize) -> F { - let low: u128 = ( rate + (state_size<<8) + (input_bits<<16) + (padding<<24) ) as u128; - let high: u128 = 1 << 64; - let domsep = F::from(high + low); - println!("domsep = {:?}", domsep ); - domsep -} +use crate::dom_sep::*; +use crate::words::*; //------------------------------------------------------------------------------ @@ -41,7 +13,7 @@ pub fn sponge_felts_pad(xs: Vec) -> F { let l: usize = xs.len(); let m: usize = l / 2; let mut state: State = zero_state(); - state.z = domain_separator(2, 3, 254, 1); + state.z = domain_separator(2, 3, 254, 1, None); for i in 0..m { state.x += xs[2*i ]; state.y += xs[2*i+1]; @@ -70,7 +42,7 @@ pub fn sponge_felts_no_pad(xs: Vec) -> F { assert!( l > 0 , "calling a sponge without padding for an empty input is not allowed"); let m: usize = l / 2; let mut state: State = zero_state(); - state.z = domain_separator(2, 3, 254, 255); + state.z = domain_separator(2, 3, 254, 255, Some(l) ); for i in 0..m { state.x += xs[2*i ]; state.y += xs[2*i+1]; @@ -89,6 +61,68 @@ pub fn sponge_felts_no_pad(xs: Vec) -> F { //------------------------------------------------------------------------------ +// hash of u64 elements with the `10*` padding stategy (rate=2, capacity=1) +pub fn sponge_u64_pad(input: Vec) -> F { + let l: usize = input.len(); + let m: usize = l / 7; + let mut state: State = zero_state(); + state.z = domain_separator(2, 3, 64, 1, None); + + for i in 0..m { + let (a,b) = u64s_to_felts( input[7*i..7*(i+1)].try_into().unwrap() ); + state.x += a; + state.y += b; + permute_inplace(&mut state); + } + + let r = l - 7*m; + let mut ws: [u64; 7] = [0; 7]; + for i in 0..r { + ws[i] = input[7*m+i]; + } + ws[r] = 1; + + let (a,b) = u64s_to_felts( ws ); + state.x += a; + state.y += b; + permute_inplace(&mut state); + + state.x +} + +//------------------------------------------------------------------------------ + +// hash of u64 elements with no padding (rate=2, capacity=1) +pub fn sponge_u64_no_pad(input: Vec) -> F { + let l: usize = input.len(); + let m: usize = l / 7; + let mut state: State = zero_state(); + state.z = domain_separator(2, 3, 64, 255, Some(l) ); + + for i in 0..m { + let (a,b) = u64s_to_felts( input[7*i..7*(i+1)].try_into().unwrap() ); + state.x += a; + state.y += b; + permute_inplace(&mut state); + } + + let r = l - 7*m; + if r > 0 { + let mut ws: [u64; 7] = [0; 7]; + for i in 0..r { + ws[i] = input[7*m+i]; + } + let (a,b) = u64s_to_felts( ws ); + state.x += a; + state.y += b; + permute_inplace(&mut state); + } + + state.x +} + +//------------------------------------------------------------------------------ + #[cfg(test)] mod tests { use super::*; @@ -112,35 +146,114 @@ mod tests { for i in 0..n { input.push( F::from( (i+1) as u64 ) ); } - println!("{} -> {:?}",n,input); + // println!("{} -> {:?}",n,input); let hash = sponge_felts_pad(input); assert_eq!( hash, expected_results_rate2[n] ); } } - #[test] fn sponge_felt_no_pad_kats() { let expected_results_rate2: [F; 8] = - [ F::from_str("12436596435061241139494609254441532101975156984785247438430218269367637564721").unwrap() - , F::from_str("13776645629657805681320936464215569484592532014724269311132107597767076186080").unwrap() - , F::from_str("15672651940583329603224775244906975133219543544569309474870924637674069574381").unwrap() - , F::from_str("18344230412728640899019354443202382658669360934913267412359635786624065034390").unwrap() - , F::from_str("17677852938281982781904616714585735700373643150759519434566383135318143611879").unwrap() - , F::from_str("5497863312684999074711437591834873546200107612265319209549992389042679138148" ).unwrap() - , F::from_str("18463022787715726219296039697387566904347367917209714404058652712879402042682").unwrap() - , F::from_str("3701269753452043827901155479778455778316871093798312420317871137452978084903" ).unwrap() + [ F::from_str("1115918818644972208256556451100635122398669465847258748217648737442977805626" ).unwrap() + , F::from_str("14606415634430706215884818096498881510473181807310833038459556078598593236816").unwrap() + , F::from_str("9485615666040940930611140660524640705765583288508158728233312031857138670757" ).unwrap() + , F::from_str("20401237157213398255038629506386304295305318378599525962978400077485609070796").unwrap() + , F::from_str("20208871202210448689110666321197722246973630737484044507918809147933228726996").unwrap() + , F::from_str("14938587838311406451824091859899313044072447315841015916269519874840159740247").unwrap() + , F::from_str("11479043188138784663840472048316951506772440497204157038728405744356281950623").unwrap() + , F::from_str("3006319728287530003674112915766568045278537432548373174348553115657244497387" ).unwrap() ]; for n in 1..9 { let mut input: Vec = vec![]; for i in 0..n { input.push( F::from( (i+1) as u64 ) ); } - println!("{} -> {:?}",n,input); + // println!("{} -> {:?}",n,input); let hash = sponge_felts_no_pad(input); assert_eq!( hash, expected_results_rate2[n-1] ); } } + #[test] + fn sponge_u64_pad_kats() { + let expected_results: [F; 26] = + [ F::from_str("17514399988141690447675586952929944437374386600212775525745235123193699555032").unwrap() + , F::from_str("15124300392866129625907254960122795910205844155666063049200988935627203491787").unwrap() + , F::from_str("13587423140487214521084491943587345128478209306845428025363074670465625624321").unwrap() + , F::from_str("12103983798657775481823964499179295155327629253022862964673907271061250824264").unwrap() + , F::from_str("20075738905477296762380600826668453184446891863732492542257921789538449731444").unwrap() + , F::from_str("10430518105738464845312764067958560485227661670628084370392094650064674199658").unwrap() + , F::from_str("20924108742969347294850424304102064711127764900339973953667556234138111787510").unwrap() + , F::from_str("968395453138586552370198024505056859000725381881719123351113644983838200204" ).unwrap() + , F::from_str("8403856038018490204768039403731489540117870413556878458592547058003244926229" ).unwrap() + , F::from_str("16773493555189025057417290550518127090656517836360451753303041522973049024060").unwrap() + , F::from_str("10796069259852617377688296009811814682856359062681790205732483337367778154920").unwrap() + , F::from_str("15397294020764383918470741938281032379900840831074050208355543125684530394430").unwrap() + , F::from_str("1905594899844509263294014245146268960239215922806331766521984979753295643902" ).unwrap() + , F::from_str("20876830728236138738089605017460220905713320987850603810408317778321908917363").unwrap() + , F::from_str("10439676373152722304438465306359532293430592776273158404192767424975467653320").unwrap() + , F::from_str("518205633402517040339835406058408206457599900552667860583791777499369334957" ).unwrap() + , F::from_str("8336177439429172243794598744828960640267423771354227970042733860391880997012" ).unwrap() + , F::from_str("9080685064607669536173711033203229621291070088551220184449841720023355805200" ).unwrap() + , F::from_str("17385687619795248602970166024979651801761808477798651323211402886029725219684").unwrap() + , F::from_str("18417161975837058148399677934835298960723961827737851459880086777820548860759").unwrap() + , F::from_str("19716747844776708500992610060411505612053548811334757970282709554641308624532").unwrap() + , F::from_str("8710645140983242543147803248831658356923859186424675188947482917691397449924" ).unwrap() + , F::from_str("20530027673279210141353437535216247451946999718955256941446828775858247330028").unwrap() + , F::from_str("5828577664570014619333209283838642006612682022514780211751976484340560101018" ).unwrap() + , F::from_str("11634491495286507638327689549013822249311934738903688618548429046204183053378").unwrap() + , F::from_str("8807597179061926120642963333889680138583084849444686960463225056442107479985" ).unwrap() + ]; + for n in 0..26 { + let mut input: Vec = vec![]; + for i in 0..n { + input.push( (i+1) as u64 ); + } + // println!("{} -> {:?}",n,input); + let hash = sponge_u64_pad(input); + assert_eq!( hash, expected_results[n] ); + } + } + + #[test] + fn sponge_u64_no_pad_kats() { + let expected_results: [F; 25] = + [ F::from_str("21622597924725733979495520095566570609749554956201434757717414014785749203159").unwrap() + , F::from_str("15070262037465553878910423448919356460240312147417573379822098585639266186611").unwrap() + , F::from_str("17800489455509024472491810131844528230790380101487728609977022937572918817580").unwrap() + , F::from_str("18482544902001991994086264832152752194342835563028328345881200344807794769093").unwrap() + , F::from_str("4896513280044258797056334181171313949942478968223215541746367832949335730719" ).unwrap() + , F::from_str("10607466914319299071570865986536377097225307429856216127936317545081254434163").unwrap() + , F::from_str("20920453842438987955159119036186533249700471511579581309756585632502753287950").unwrap() + , F::from_str("10142584335678051072230022498141744715568278869207119228015367697171636332483").unwrap() + , F::from_str("8127573586370924616640504816002170182366974095264816809743966109465196084712" ).unwrap() + , F::from_str("20501115081171281094927237732262757083689925310922435884195895336705387283312").unwrap() + , F::from_str("21718736237191095099244323827903849179538431290485808056798870455448217925093").unwrap() + , F::from_str("5329770041250619031368715837636171331829813019969012081505727572188217732934" ).unwrap() + , F::from_str("5536718914992748414523347895616925070211178854135748328150567266300552067485" ).unwrap() + , F::from_str("2342299046320660948203954171575512853623883371738280507120380123090358747132" ).unwrap() + , F::from_str("17453198006826074954036266521394818291071518114598196144854794710197222107246").unwrap() + , F::from_str("21690412374475433925321578588113530111207813306323496795476840269069535072168").unwrap() + , F::from_str("917342963235263089117027572485105782705895038326149594009829794659181334451" ).unwrap() + , F::from_str("814652782599295015685819882207503906021843279393495868703807596344336059332" ).unwrap() + , F::from_str("8618671375832156391944304266004989498789486790466471210906461400013981588954" ).unwrap() + , F::from_str("20775280237828871312555473717074489898073475363933916673847313267274833513741").unwrap() + , F::from_str("4959358925764805024322589850837173061872468998088908390781158651152414358200" ).unwrap() + , F::from_str("6722516458366036816475139923425349719879460303424067286694084614694693662945" ).unwrap() + , F::from_str("10918347824743901498913169606811754277556665472042558262426391959263833235902").unwrap() + , F::from_str("1920831798438862469031092832500327049860677246669734790815791649814404269702" ).unwrap() + , F::from_str("2950115377168425877897898851550119758110847105372644043990473657177484603663" ).unwrap() + ]; + for n in 1..26 { + let mut input: Vec = vec![]; + for i in 0..n { + input.push( (i+1) as u64 ); + } + println!("{} -> {:?}",n,input); + let hash = sponge_u64_no_pad(input); + assert_eq!( hash, expected_results[n-1] ); + } + } } diff --git a/src/words.rs b/src/words.rs new file mode 100644 index 0000000..c5e88d6 --- /dev/null +++ b/src/words.rs @@ -0,0 +1,67 @@ + +use lazy_static::lazy_static; + +use ark_ff::{PrimeField}; +use ark_ff::biginteger::{BigInt}; +use ark_bn254::Fr as F; + +//------------------------------------------------------------------------------ + +const BIGINT_TWO_TO_64: BigInt<4> = BigInt( [0,1,0,0] ); +const BIGINT_TWO_TO_128: BigInt<4> = BigInt( [0,0,1,0] ); +const BIGINT_TWO_TO_192: BigInt<4> = BigInt( [0,0,0,1] ); + +lazy_static! { + static ref field_powers_of_two_to_64: [F;3] = + [ F::from_bigint(BIGINT_TWO_TO_64 ).unwrap() + , F::from_bigint(BIGINT_TWO_TO_128).unwrap() + , F::from_bigint(BIGINT_TWO_TO_192).unwrap() + ]; +} + +// we want to consume u64-s (more precisely, Goldilocks field elements, but +// absorbing u64-s is more general). Unfortuntaly, four u64-s just don't +// fit into a BN254 field element, so instead we will pack seven u64-s into +// two field elements (as we are using rate=2). +// +pub fn u64s_to_felts( ws: [u64;7] ) -> (F,F) { + let hi = ws[6] >> 32; + let lo = ws[6] & 0xFFFF_FFFF; + + let x = F::from(ws[0]) + + field_powers_of_two_to_64[0] * F::from(ws[1]) + + field_powers_of_two_to_64[1] * F::from(ws[2]) + + field_powers_of_two_to_64[2] * F::from(lo ); + + let y = F::from(ws[3]) + + field_powers_of_two_to_64[0] * F::from(ws[4]) + + field_powers_of_two_to_64[1] * F::from(ws[5]) + + field_powers_of_two_to_64[2] * F::from(hi ); + + (x,y) +} + +//------------------------------------------------------------------------------ + +#[cfg(test)] +mod tests { + use super::*; + use ark_std::str::FromStr; + + #[test] + fn u64s_to_felts_kat() { + let input: [u64; 7] = + [ 0x_1234_5678_9abc_def0 + , 0x_dead_cafe_9876_0123 + , 0x_fc24_78a3_9d06_8818 + , 0x_365d_9035_1967_7d70 + , 0x_4acb_9086_8bb1_b970 + , 0x_df46_f82e_1f13_59d0 + , 0x_3dbb_3705_9b6e_b13f + ]; + let (x,y) = u64s_to_felts( input ); + assert_eq!( x, F::from_str("16368941413626455547165950020765495089037394581179785942639960710896").unwrap() ); + assert_eq!( y, F::from_str("6501065548289439305465519268972294694155486997049983265694024170864" ).unwrap() ); + } + +}