2020-02-14 15:39:31 +00:00
use super ::error ::Result ;
2021-12-10 13:43:59 +00:00
use kuska_sodiumoxide ::crypto ::hash ::sha256 ;
2020-01-05 16:11:00 +00:00
use serde_json ::Value ;
2020-01-05 22:15:43 +00:00
pub fn ssb_sha256 ( v : & Value ) -> Result < sha256 ::Digest > {
2021-12-10 13:43:59 +00:00
let v8encoding = stringify_json ( v ) ?
2020-01-05 16:11:00 +00:00
. encode_utf16 ( )
. map ( | ch_u16 | ( ch_u16 & 0xff ) as u8 )
. collect ::< Vec < u8 > > ( ) ;
Ok ( sha256 ::hash ( & v8encoding [ .. ] ) )
}
2020-01-05 22:15:43 +00:00
pub fn stringify_json ( v : & Value ) -> Result < String > {
2020-01-05 16:11:00 +00:00
fn spaces ( n : usize ) -> & 'static str {
& " " [ .. 2 * n ]
}
// see https://www.ecma-international.org/ecma-262/6.0/#sec-serializejsonobject
2020-01-05 22:15:43 +00:00
fn append_json ( buffer : & mut String , level : usize , v : & Value ) -> Result < ( ) > {
2020-01-05 16:11:00 +00:00
match v {
Value ::Object ( values ) = > {
if values . is_empty ( ) {
buffer . push_str ( " {} " ) ;
} else {
buffer . push_str ( " { \n " ) ;
for ( i , ( key , value ) ) in values . iter ( ) . enumerate ( ) {
buffer . push_str ( spaces ( level + 1 ) ) ;
2020-01-05 22:15:43 +00:00
buffer . push_str ( & serde_json ::to_string ( & key ) ? ) ;
2020-01-05 16:11:00 +00:00
buffer . push_str ( " : " ) ;
2021-12-10 13:43:59 +00:00
append_json ( buffer , level + 1 , value ) ? ;
2020-01-05 16:11:00 +00:00
if i < values . len ( ) - 1 {
buffer . push ( ',' ) ;
}
buffer . push ( '\n' ) ;
}
buffer . push_str ( spaces ( level ) ) ;
buffer . push ( '}' ) ;
}
}
Value ::Array ( values ) = > {
if values . is_empty ( ) {
buffer . push_str ( " [] " ) ;
} else {
buffer . push_str ( " [ \n " ) ;
for ( i , value ) in values . iter ( ) . enumerate ( ) {
buffer . push_str ( spaces ( level + 1 ) ) ;
2021-12-10 13:43:59 +00:00
append_json ( buffer , level + 1 , value ) ? ;
2020-01-05 16:11:00 +00:00
if i < values . len ( ) - 1 {
buffer . push ( ',' ) ;
}
buffer . push ( '\n' ) ;
}
buffer . push_str ( spaces ( level ) ) ;
buffer . push ( ']' ) ;
}
}
Value ::String ( value ) = > {
2020-01-05 22:15:43 +00:00
buffer . push_str ( & serde_json ::to_string ( & value ) ? ) ;
2020-01-05 16:11:00 +00:00
}
Value ::Number ( value ) = > {
let mut as_str = value . to_string ( ) ;
2021-12-10 13:43:59 +00:00
if as_str . contains ( 'e' ) & & ! as_str . contains ( " e- " ) & & ! as_str . contains ( " e+ " ) {
2020-02-14 15:39:31 +00:00
as_str = as_str . replace ( " e " , " e+ " )
}
2020-01-05 16:11:00 +00:00
buffer . push_str ( & as_str ) ;
}
Value ::Bool ( value ) = > {
buffer . push_str ( if * value { " true " } else { " false " } ) ;
}
Value ::Null = > {
buffer . push_str ( " null " ) ;
}
}
Ok ( ( ) )
}
let mut result = String ::new ( ) ;
2021-12-10 13:43:59 +00:00
append_json ( & mut result , 0 , v ) ? ;
2020-01-05 16:11:00 +00:00
Ok ( result )
}
#[ cfg(test) ]
mod test {
use super ::* ;
2020-02-14 15:39:31 +00:00
const JSON : & str = r # "{"a":0,"b":1.1,"c":null,"d":true,"f":false,"g":{},"h":{"h1":1},"i":[],"j":[1],"k":[1,2]}"# ;
2020-01-05 16:11:00 +00:00
#[ test ]
2020-01-05 22:15:43 +00:00
fn test_json_stringify ( ) -> Result < ( ) > {
let v : Value = serde_json ::from_str ( JSON ) ? ;
2020-01-05 16:11:00 +00:00
let json = stringify_json ( & v ) ? ;
let expected = r #" {
" a " : 0 ,
" b " : 1.1 ,
" c " : null ,
" d " : true ,
" f " : false ,
" g " : { } ,
" h " : {
" h1 " : 1
} ,
" i " : [ ] ,
" j " : [
1
] ,
" k " : [
1 ,
2
]
} " #;
assert_eq! ( expected , json ) ;
Ok ( ( ) )
}
#[ test ]
2020-01-05 22:15:43 +00:00
fn test_verify_known_msg_integrity ( ) -> Result < ( ) > {
2020-01-05 16:11:00 +00:00
let expected = " Cg0ZpZ8cV85G8UIIropgBOvM8+Srlv9LSGDNGnpdK44= " ;
let message = r # "{"previous":"%seUEAo7PTyA7vNwnOrmGIsUFfpyRzOvzGVv1QCb/Fz8=.sha256","author":"@BIbVppzlrNiRJogxDYz3glUS7G4s4D4NiXiPEAEzxdE=.ed25519","sequence":37,"timestamp":1439392020612,"hash":"sha256","content":{"type":"post","text":"@paul real time replies didn't work.","repliesTo":"%xWKunF6nXD7XMC+D4cjwDMZWmBnmRu69w9T25iLNa1Q=.sha256","mentions":["%7UKRfZb2u8al4tYWHqM55R9xpE/KKVh9U0M6BdugGt4=.sha256"],"recps":[{"link":"@hxGxqPrplLjRG2vtjQL87abX4QKqeLgCwQpS730nNwE=.ed25519","name":"paul"}]},"signature":"gGxSPdBJZxp6x5f3HzQGoQSeSdh/C5AtymIn+miWa+lcC6DdqpRSgaeH9KHeLf+/CKhU6REYIpWaLr4CKDMfCg==.sig.ed25519"}"# ;
let message_value : Value = serde_json ::from_str ( & message ) ? ;
let current = base64 ::encode ( & ssb_sha256 ( & message_value ) ? ) ;
assert_eq! ( expected , current ) ;
Ok ( ( ) )
}
#[ test ]
2020-01-05 22:15:43 +00:00
fn test_msg_with_float_mantissa ( ) -> Result < ( ) > {
2020-01-05 16:11:00 +00:00
let expected = " RUcldndjJUkEcZ5hX6zAj/xLlnh0n4BZ6ThJOW5RvIk= " ;
let message = r # "{"previous":"%gbem82xZNVHbOM2pyOlxymsAfstdMFfGSoawWQtObX8=.sha256","author":"@TXKFQehlyoSn8UJAIVP/k2BjFINC591MlBC2e2d24mA=.ed25519","sequence":1557,"timestamp":1495245157893,"hash":"sha256","content":{"type":"post","transactionHash":9.691449834862513e+76,"address":7.073631810716965e+46,"event":"ActionAdded","text":"{\"actionID\":\"1\",\"amount\":\"0\",\"description\":\"Bind Ethereum events to Secure Scuttlebutt posts\"}}"},"signature":"/Qvm9ozEfl0Thyvs+mnwhLDReZ8xeKXA3hSXOxm53SFkLEnnJ+IF0l7LSqc56Y3vl8FwarJ6k0PGmcU3U8FMAw==.sig.ed25519"}"# ;
2020-02-14 15:39:31 +00:00
let message_value : Value = serde_json ::from_str ( & message ) ? ;
let current = base64 ::encode ( & ssb_sha256 ( & message_value ) ? ) ;
2020-01-05 16:11:00 +00:00
assert_eq! ( expected , current ) ;
Ok ( ( ) )
}
#[ test ]
2020-01-05 22:15:43 +00:00
fn test_msg_with_float_precision ( ) -> Result < ( ) > {
2020-01-05 16:11:00 +00:00
let expected = " BUtTVIJyN5fUXzQy2uQfCCzlAg0s6laQQqFIu+kGnFM= " ;
let message = r # "{"previous":"%ButTjV+H9VfONhX+lLbJb5LR+W14SFqbmjOfdMPZ5+4=.sha256","sequence":15034,"author":"@6ilZq3kN0F+dXFHAPjAwMm87JEb/VdB+LC9eIMW3sa0=.ed25519","timestamp":1567190273951.0159,"hash":"sha256","content":{"type":"vote","channel":null,"vote":{"link":"%GvtUsekEwsCj1cQ6+4Gihkm+ek99BhB537g1xUKjhsA=.sha256","value":1,"expression":"Like"}},"signature":"UkVfqDmBhHrDfMvFT8iUhEispAku/zbdXKCyRVlxYp2wNtJ4okwKE7hTkKhbiMVA7sGIV5dzHZyMotXCL46iDw==.sig.ed25519"}"# ;
2020-02-14 15:39:31 +00:00
let message_value : Value = serde_json ::from_str ( & message ) ? ;
let current = base64 ::encode ( & ssb_sha256 ( & message_value ) ? ) ;
2020-01-05 16:11:00 +00:00
assert_eq! ( expected , current ) ;
Ok ( ( ) )
}
}