char/plc-mirror
git clone https://git.t4t.associates/char/plc-mirror
c2f2166
main
1use anyhow::{ Context , Result , bail, ensure}; 2use base64::{ Engine , engine:: general_purpose:: URL_SAFE_NO_PAD }; 3use chrono::{ DateTime , SecondsFormat , Utc }; 4use cid:: multibase::{ self , Base }; 5use serde_json::{ Map , Value , json}; 6 7const STRING : u8 =0 ; 8const INTERN : u8 =1 ; 9const KEY : u8 =2 ; 10const SIGNATURE : u8 =3 ; 11const CID : u8 =4 ; 12const ARRAY : u8 =5 ; 13const OBJECT : u8 =6 ; 14const BSKY_GENESIS : u8 =7 ; 15const TRUMP_GENESIS : u8 =8 ; 16const LITERAL_CBOR : u8 =9 ; 17const HANDLE : u8 =10 ; 18const BSKY_CREATE : u8 =11 ; 19 20const MAX_PROGRAM : usize =1024 * 1024 ; 21const MAX_DEPTH : usize =64 ; 22const MAX_STACK : usize =16_384 ; 23const MAX_WORK : usize =16 * 1024 * 1024 ; 24 25// These are format constants, not a description of Bluesky's current configuration. 26pub ( crate ) const BSKY_ROTATIONS : [ & str ; 2 ] =[ 27"did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg" , 28"did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK" , 29]; 30const TRUMP_ALIASES : [ & str ; 7 ] =[ 31"at://donald.trump.com" , 32"at://jr.donald.trump.com" , 33"at://eric.trump.com" , 34"at://ivanka.trump.com" , 35"at://tiffany.trump.com" , 36"at://barron.trump.com" , 37"at://melania.trump.com" , 38]; 39 40fn varint ( mut n : u64 , out : & mut Vec < u8 >) { 41while n >=128 { 42 out. push ( nas u8 |128 ); 43 n >>=7 ; 44} 45 out. push ( nas u8 ); 46} 47 48fn literal ( tag : u8 , bytes : & [ u8 ], out : & mut Vec < u8 >) { 49 out. push ( tag); 50varint ( bytes. len () as u64 , out); 51 out. extend_from_slice ( bytes); 52} 53 54struct Reader < ' a >( &' a [ u8 ]); 55 56impl < ' a > Reader < ' a > { 57fn take ( & mut self , n : usize ) ->Result < &' a [ u8 ]> { 58ensure! ( n <=self . 0 . len (), "truncated operand" ); 59let ( head, tail) =self . 0 . split_at ( n); 60self . 0 = tail; 61Ok ( head) 62} 63 64fn varint ( & mut self ) ->Result < u64 > { 65let mut n =0 ; 66for shiftin ( 0 ..70 ). step_by ( 7 ) { 67let b =self . take ( 1 ) ?[ 0 ]; 68ensure! ( shift !=63 || b <=1 , "varint overflow" ); 69 n |= u64:: from ( b& 127 ) << shift; 70if b <128 { 71return Ok ( n); 72} 73} 74bail! ( "varint overflow" ) 75} 76 77fn bytes ( & mut self ) ->Result < &' a [ u8 ]> { 78let n = usize:: try_from ( self . varint () ?) ?; 79self . take ( n) 80} 81} 82 83fn genesis ( endpoint : Value , aliases : Value , rotations : Value , key : Value , sig : Value ) ->Value { 84json! ({ 85"type" : "plc_operation" , 86"prev" : null, 87"rotationKeys" : rotations, 88"alsoKnownAs" : aliases, 89"services" : { "atproto_pds" : { " type " : "AtprotoPersonalDataServer" , "endpoint" : endpoint}}, 90"verificationMethods" : { "atproto" : key}, 91"sig" : sig, 92}) 93} 94 95fn legacy_create ( service : Value , handle : Value , key : Value , sig : Value ) ->Value { 96json! ({ 97"type" : "create" , 98"prev" : null, 99"recoveryKey" : BSKY_ROTATIONS [ 0 ], 100"service" : service, 101"handle" : handle, 102"signingKey" : key, 103"sig" : sig, 104}) 105} 106 107pub fn encode ( value : & Value , intern : impl Fn ( & str ) ->Option < u64 >) ->Result < Vec < u8 >> { 108let depth =value_depth ( value); 109ensure! ( depth <=MAX_DEPTH , "operation too deep" ); 110// JSON text does not round-trip every f64 with serde_json's default parser. 111let fallback = serde_ipld_dagcbor:: to_vec ( value) ?; 112ensure! ( fallback. len () <=MAX_PROGRAM -11 , "operation too large" ); 113let mut out =Vec :: new (); 114// Wide/deep values use a single literal rather than exhausting the VM's work budget. 115if fallback. len () <=MAX_STACK /2 && fallback. len () * 6 * ( depth +2 ) <=MAX_WORK { 116encode_value ( value, & intern, & mut out, 0 ) ?; 117} 118if out. is_empty () || out. len () > fallback. len () { 119 out. clear (); 120literal ( LITERAL_CBOR , & fallback, & mut out); 121} 122Ok ( out) 123} 124 125fn encode_value ( 126value : & Value , 127intern : & impl Fn ( & str ) ->Option < u64 >, 128out : & mut Vec < u8 >, 129depth : usize , 130) ->Result <()> { 131ensure! ( depth <=MAX_DEPTH , "operation too deep" ); 132if value. get ( "type" ). and_then ( Value :: as_str) ==Some ( "create" ) { 133let service =& value[ "service" ]; 134let handle =& value[ "handle" ]; 135let key =& value[ "signingKey" ]; 136let sig =& value[ "sig" ]; 137if [ service, handle, key, sig]. iter (). all ( |v| v. is_string ()) 138 && value ==& legacy_create ( service. clone (), handle. clone (), key. clone (), sig. clone ()) 139{ 140for operandin [ service, handle, key, sig] { 141encode_value ( operand, intern, out, depth +1 ) ?; 142} 143 out. push ( BSKY_CREATE ); 144return Ok (()); 145} 146} 147if value. get ( "type" ). and_then ( Value :: as_str) ==Some ( "plc_operation" ) { 148let endpoint =& value[ "services" ][ "atproto_pds" ][ "endpoint" ]; 149let aliases =& value[ "alsoKnownAs" ]; 150let rotations =& value[ "rotationKeys" ]; 151let key =& value[ "verificationMethods" ][ "atproto" ]; 152let sig =& value[ "sig" ]; 153if endpoint. is_string () && key. is_string () && sig. is_string () { 154if rotations ==& json! ( BSKY_ROTATIONS ) 155 && aliases 156. as_array () 157. is_some_and ( |a| a. len () ==1 && a[ 0 ]. is_string ()) 158 && value 159 ==& genesis ( 160 endpoint. clone (), 161 aliases. clone (), 162 rotations. clone (), 163 key. clone (), 164 sig. clone (), 165) 166{ 167for operandin [ endpoint, & aliases[ 0 ], key, sig] { 168encode_value ( operand, intern, out, depth +1 ) ?; 169} 170 out. push ( BSKY_GENESIS ); 171return Ok (()); 172} 173if endpoint =="https://pds.trump.com" 174 && aliases ==& json! ( TRUMP_ALIASES ) 175 && rotations 176. as_array () 177. is_some_and ( |a| a. len () ==4 && a. iter (). all ( Value :: is_string)) 178 && value 179 ==& genesis ( 180 endpoint. clone (), 181 aliases. clone (), 182 rotations. clone (), 183 key. clone (), 184 sig. clone (), 185) 186{ 187for operandin rotations. as_array (). unwrap (). iter (). chain ([ key, sig]) { 188encode_value ( operand, intern, out, depth +1 ) ?; 189} 190 out. push ( TRUMP_GENESIS ); 191return Ok (()); 192} 193} 194} 195match value{ 196Value :: String ( s) =>{ 197if let Some ( id) =intern ( s) { 198 out. push ( INTERN ); 199varint ( id, out); 200} else if let Some ( handle) = s 201. strip_prefix ( "at://" ) 202. and_then ( |s| s. strip_suffix ( ".bsky.social" )) 203{ 204literal ( HANDLE , handle. as_bytes (), out); 205} else if let Some ( key) = s. strip_prefix ( "did:key:z" ) 206 && key. len () <=128 207 &&let Ok ( bytes) =Base :: Base58Btc . decode ( key) 208 &&Base :: Base58Btc . encode ( & bytes) == key 209{ 210literal ( KEY , & bytes, out); 211} else if s. len () ==86 212 &&let Ok ( bytes) =URL_SAFE_NO_PAD . decode ( s) 213 && bytes. len () ==64 214 &&URL_SAFE_NO_PAD . encode ( & bytes) ==* s 215{ 216 out. push ( SIGNATURE ); 217 out. extend_from_slice ( & bytes); 218} else if let Ok ( cid) = s. parse ::< cid:: Cid >() 219 && cid. to_string () ==* s 220{ 221literal ( CID , & cid. to_bytes (), out); 222} else { 223literal ( STRING , s. as_bytes (), out); 224} 225} 226Value :: Array ( values) =>{ 227for valuein values{ 228encode_value ( value, intern, out, depth +1 ) ?; 229} 230 out. push ( ARRAY ); 231varint ( values. len () as u64 , out); 232} 233Value :: Object ( values) =>{ 234for ( key, value) in values{ 235encode_value ( & Value :: String ( key. clone ()), intern, out, depth +1 ) ?; 236encode_value ( value, intern, out, depth +1 ) ?; 237} 238 out. push ( OBJECT ); 239varint ( values. len () as u64 , out); 240} 241 _ =>literal ( LITERAL_CBOR , & serde_ipld_dagcbor:: to_vec ( value) ?, out), 242} 243Ok (()) 244} 245 246pub fn decode < ' a >( program : & [ u8 ], intern : impl Fn ( usize ) ->Option < &' a str >) ->Result < Value > { 247ensure! ( program. len () <=MAX_PROGRAM , "program too large" ); 248let mut input =Reader ( program); 249let mut stack: Vec <( Value , usize )> =Vec :: new (); 250let mut work =0usize ; 251while !input. 0 . is_empty () { 252let opcode = input. take ( 1 ) ?[ 0 ]; 253let ( value, depth) =match opcode{ 254STRING |HANDLE =>{ 255let s = std:: str:: from_utf8 ( input. bytes () ?) ?; 256( 257Value :: String ( if opcode ==HANDLE { 258format! ( "at://{s}.bsky.social" ) 259} else { 260 s. to_owned () 261}), 2620 , 263) 264} 265INTERN =>{ 266let id = usize:: try_from ( input. varint () ?) ?; 267let s =intern ( id). context ( "missing interned string" ) ?; 268ensure! ( s. len () <=MAX_PROGRAM , "interned string too large" ); 269( Value :: String ( s. to_owned ()), 0 ) 270} 271KEY =>{ 272let bytes = input. bytes () ?; 273ensure! ( bytes. len () <=128 , "key operand too large" ); 274( 275Value :: String ( format! ( 276"did:key:{}" , 277 multibase:: encode ( Base :: Base58Btc , bytes) 278)), 2790 , 280) 281} 282SIGNATURE =>( Value :: String ( URL_SAFE_NO_PAD . encode ( input. take ( 64 ) ?)), 0 ), 283CID =>( 284Value :: String ( cid:: Cid :: try_from ( input. bytes () ?) ?. to_string ()), 2850 , 286), 287LITERAL_CBOR =>{ 288let value: Value = serde_ipld_dagcbor:: from_slice ( input. bytes () ?) ?; 289let depth =value_depth ( & value); 290( value, depth) 291} 292ARRAY |OBJECT |BSKY_GENESIS |TRUMP_GENESIS |BSKY_CREATE =>{ 293let n =match opcode{ 294ARRAY => usize:: try_from ( input. varint () ?) ?, 295OBJECT => usize:: try_from ( input. varint () ?) ? 296. checked_mul ( 2 ) 297. context ( "object size overflow" ) ?, 298BSKY_GENESIS |BSKY_CREATE =>4 , 299 _ =>6 , 300}; 301ensure! ( n <= stack. len (), "stack underflow" ); 302let start = stack. len () - n; 303let depth = stack[ start..]. iter (). map ( |( _, d) | d +1 ). max (). unwrap_or ( 1 ); 304let mut values = stack. drain ( start..). map ( |( v, _) | v); 305let value =match opcode{ 306ARRAY =>Value :: Array ( values. collect ()), 307OBJECT =>{ 308let mut object =Map :: new (); 309while let Some ( key) = values. next () { 310let Value :: String ( key) = keyelse { 311bail! ( "object key is not a string" ) 312}; 313ensure! ( 314 object. insert ( key, values. next (). unwrap ()). is_none (), 315"duplicate object key" 316); 317} 318Value :: Object ( object) 319} 320BSKY_GENESIS |BSKY_CREATE =>{ 321let args: Vec < _ > = values. collect (); 322ensure! ( 323 args. iter (). all ( Value :: is_string), 324"invalid genesis operands" 325); 326let [ endpoint, name, key, sig]: [ Value ; 4 ] = args. try_into (). unwrap (); 327if opcode ==BSKY_CREATE { 328legacy_create ( endpoint, name, key, sig) 329} else { 330genesis ( endpoint, json! ([ name]), json! ( BSKY_ROTATIONS ), key, sig) 331} 332} 333 _ =>{ 334let args: Vec < _ > = values. collect (); 335ensure! ( 336 args. iter (). all ( Value :: is_string), 337"invalid genesis operands" 338); 339let [ a, b, c, d, key, sig]: [ Value ; 6 ] = args. try_into (). unwrap (); 340genesis ( 341json! ( "https://pds.trump.com" ), 342json! ( TRUMP_ALIASES ), 343json! ([ a, b, c, d]), 344 key, 345 sig, 346) 347} 348}; 349( 350 value, 351if opcode ==BSKY_GENESIS || opcode ==TRUMP_GENESIS { 352 depth +2 353} else { 354 depth 355}, 356) 357} 358 _ =>bail! ( "unknown opcode {opcode}" ), 359}; 360ensure! ( depth <=MAX_DEPTH , "operation too deep" ); 361// Bound cumulative construction work, including dictionary and template expansion. 362 work = work 363. checked_add ( serde_json:: to_vec ( & value) ?. len ()) 364. context ( "expansion overflow" ) ?; 365ensure! ( work <=MAX_WORK , "program expansion limit exceeded" ); 366ensure! ( stack. len () <MAX_STACK , "stack limit exceeded" ); 367 stack. push (( value, depth)); 368} 369ensure! ( stack. len () ==1 , "program must produce exactly one value" ); 370Ok ( stack. pop (). unwrap (). 0 ) 371} 372 373fn value_depth ( value : & Value ) ->usize { 374match value{ 375Value :: Array ( a) =>1 + a. iter (). map ( value_depth). max (). unwrap_or ( 0 ), 376Value :: Object ( o) =>1 + o. values (). map ( value_depth). max (). unwrap_or ( 0 ), 377 _ =>0 , 378} 379} 380 381pub fn encode_did ( did : & str ) ->Vec < u8 > { 382if let Some ( suffix) = did. strip_prefix ( "did:plc:" ) 383 &&let Ok ( bytes) =Base :: Base32Lower . decode ( suffix) 384 && bytes. len () ==15 385 &&Base :: Base32Lower . encode ( & bytes) == suffix 386{ 387let mut out =vec! [ 1 ]; 388 out. extend ( bytes); 389return out; 390} 391let mut out =vec! [ 0 ]; 392 out. extend_from_slice ( did. as_bytes ()); 393 out 394} 395 396pub fn decode_did ( bytes : & [ u8 ]) ->Result < String > { 397match bytes. split_first () { 398Some (( 0 , text)) =>Ok ( std:: str:: from_utf8 ( text) ?. to_owned ()), 399Some (( 1 , bytes)) if bytes. len () ==15 =>{ 400Ok ( format! ( "did:plc:{}" , Base :: Base32Lower . encode ( bytes))) 401} 402 _ =>bail! ( "invalid DID encoding" ), 403} 404} 405 406pub fn encode_timestamp ( text : & str ) ->Vec < u8 > { 407if let Ok ( time) =DateTime :: parse_from_rfc3339 ( text) { 408let ms = time. timestamp_millis (); 409if let Some ( time) =DateTime ::< Utc >:: from_timestamp_millis ( ms) 410 && time. to_rfc3339_opts ( SecondsFormat :: Millis , true ) == text 411{ 412let mut out =vec! [ 1 ]; 413varint ((( msas u64 ) <<1 ) ^(( ms >>63 ) as u64 ), & mut out); 414return out; 415} 416} 417let mut out =vec! [ 0 ]; 418 out. extend_from_slice ( text. as_bytes ()); 419 out 420} 421 422pub fn decode_timestamp ( bytes : & [ u8 ]) ->Result < String > { 423match bytes. split_first () { 424Some (( 0 , text)) =>Ok ( std:: str:: from_utf8 ( text) ?. to_owned ()), 425Some (( 1 , bytes)) =>{ 426let mut input =Reader ( bytes); 427let n = input. varint () ?; 428ensure! ( input. 0 . is_empty (), "trailing timestamp bytes" ); 429let ms =( n >>1 ) as i64 ^ -(( n& 1 ) as i64 ); 430let time = 431DateTime ::< Utc >:: from_timestamp_millis ( ms). context ( "timestamp out of range" ) ?; 432Ok ( time. to_rfc3339_opts ( SecondsFormat :: Millis , true )) 433} 434 _ =>bail! ( "invalid timestamp encoding" ), 435} 436} 437 438# [ cfg ( test )] 439mod tests{ 440use super :: * ; 441 442# [ test ] 443fn legacy_bluesky_creates_are_compact_and_lossless () ->Result <()> { 444let original =json! ({ 445"handle" : "paul.bsky.social" , 446"prev" : null, 447"recoveryKey" : "did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg" , 448"service" : "https://bsky.social" , 449"sig" : "DyaPWDItkJnVkN1izINSW-fdjUzP9BkIKlD7SnzD5axfK_870ZZ-1EYcrQLQtP9VkWcp2cdbyIHprjPfeUs8WQ" , 450"signingKey" : "did:key:zQ3shP5TBe1sQfSttXty15FAEHV1DZgcxRZNxvEWnPfLFwLxJ" , 451"type" : "create" , 452}); 453let strings =[ "https://bsky.social" ]; 454let program =encode ( & original, |s|{ 455 strings. iter (). position ( |v|* v == s). map ( |n| nas u64 ) 456}) ?; 457assert! ( program. len () <=130 , "{} bytes" , program. len ()); 458println! ( "legacy create: {} bytes" , program. len ()); 459assert_eq! ( decode ( & program, |id| strings. get ( id). copied ()) ?, original); 460 461let mut variants =vec! [ original. clone ()]; 462for fieldin original. as_object (). unwrap (). keys () { 463let mut absent = original. clone (); 464 absent. as_object_mut (). unwrap (). remove ( field); 465 variants. push ( absent); 466let mut null = original. clone (); 467 null[ field] =Value :: Null ; 468 variants. push ( null); 469} 470let mut custom = original. clone (); 471 custom[ "recoveryKey" ] = original[ "signingKey" ]. clone (); 472 variants. push ( custom); 473let mut extra = original; 474 extra[ "future" ] =json! ({ "nested" : [ false , null, 7 ]}); 475 variants. push ( extra); 476for valuein variants{ 477let program =encode ( & value, |_|None ) ?; 478let decoded =decode ( & program, |_|None ) ?; 479assert_eq! ( 480 serde_ipld_dagcbor:: to_vec ( & decoded) ?, 481 serde_ipld_dagcbor:: to_vec ( & value) ? 482); 483} 484Ok (()) 485} 486 487# [ test ] 488fn values_and_canonical_bytes_survive () ->Result <()> { 489let values =[ 490json! ({ "type" : "create" , "prev" : null, "extra" :[ true , false , -7 , 1.5 , {}, []]}), 491json! ({ "type" : "plc_tombstone" , "prev" : "bafyreih553tggj3y22ewyb5mlhrcszkjjles7fr57xixixf3sk52jeqoyy" , "sig" : "literal" }), 492json! ({ "alsoKnownAs" :[ "at://alice.bsky.social" , "at : //☃.example" ], "unknown" :{ "n" : 18446744073709551615u64 }}), 493json! ( -0.0 ), 494]; 495let strings =[ "at://☃.example" . to_owned ()]; 496for valuein values{ 497let program =encode ( & value, |s|{ 498 strings. iter (). position ( |v| v == s). map ( |n| nas u64 ) 499}) ?; 500let decoded =decode ( & program, |id| strings. get ( id). map ( String :: as_str)) ?; 501assert_eq! ( decoded, value); 502assert_eq! ( 503 serde_ipld_dagcbor:: to_vec ( & decoded) ?, 504 serde_ipld_dagcbor:: to_vec ( & value) ? 505); 506} 507Ok (()) 508} 509 510# [ test ] 511fn metadata_is_exact () ->Result <()> { 512for didin [ 513"did:plc:ragtjsm2j2vknwkz3zp4oxrd" , 514"did:web:example.com" , 515"did:plc:RAGT" , 516"" , 517] { 518assert_eq! ( decode_did ( & encode_did ( did)) ?, did); 519} 520for timein [ 521"2026-09-12T12:16:35.070Z" , 522"1960-01-01T00:00:00.000Z" , 523"2026-09-12T12:16:35Z" , 524"2026-09-12T12:16:35.070123Z" , 525"2026-09-12T13:16:35.070+01:00" , 526"unknown" , 527] { 528assert_eq! ( decode_timestamp ( & encode_timestamp ( time)) ?, time); 529} 530Ok (()) 531} 532 533# [ test ] 534fn malformed_programs_fail () { 535for bytesin [ 536vec! [], 537vec! [ 255 ], 538vec! [ INTERN , 0 ], 539vec! [ STRING , 3 , b'a' ], 540vec! [ ARRAY , 1 ], 541vec! [ OBJECT , 1 ], 542vec! [ BSKY_GENESIS ], 543vec! [ BSKY_CREATE ], 544vec! [ 545LITERAL_CBOR , 5461 , 5470xf6 , 548STRING , 5490 , 550STRING , 5510 , 552STRING , 5530 , 554BSKY_CREATE , 555], 556vec! [ SIGNATURE , 0 ], 557vec! [ CID , 1 , 0 ], 558vec! [ STRING , 1 , 255 ], 559vec! [ INTERN , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 2 ], 560vec! [ STRING , 0 , STRING , 0 ], 561vec! [ STRING , 0 , STRING , 0 , STRING , 0 , STRING , 0 , OBJECT , 2 ], 562] { 563assert! ( decode ( & bytes, |_|None ). is_err (), "{bytes:?}" ); 564} 565let mut nested =vec! [ STRING , 0 ]; 566for _in 0 ..100 { 567 nested. extend ([ ARRAY , 1 ]); 568} 569assert! ( decode ( & nested, |_|None ). is_err ()); 570} 571}