add sponge for u64 inputs

This commit is contained in:
Balazs Komuves 2025-01-21 14:55:59 +01:00
parent 79c74f8bbd
commit ddc0f00d41
No known key found for this signature in database
GPG Key ID: F63B7AEF18435562
6 changed files with 387 additions and 63 deletions

View File

@ -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
--------------------------------------------------------------------------------

42
reference/src/U64.hs Normal file
View File

@ -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
]
--------------------------------------------------------------------------------

46
src/dom_sep.rs Normal file
View File

@ -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<usize>) -> 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
}
//------------------------------------------------------------------------------

View File

@ -1,3 +1,7 @@
mod constants;
mod dom_sep;
mod words;
pub mod permutation;
pub mod sponge;

View File

@ -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>) -> 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>) -> 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>) -> F {
//------------------------------------------------------------------------------
// hash of u64 elements with the `10*` padding stategy (rate=2, capacity=1)
pub fn sponge_u64_pad(input: Vec<u64>) -> 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<u64>) -> 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<F> = 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<u64> = 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<u64> = 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] );
}
}
}

67
src/words.rs Normal file
View File

@ -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() );
}
}