StrongED$mode = BasAsm - : : $;" at line ";:"Spool":   bits%=17 challenge$="fluffybunny" output$="RAM:X-Hashcash"  length%=1500  / W% 83*4 : 80 words (plus a little spare)  dateblock% &24,UTC% 5 zero(W%,80*4)  zero blocks  today$=date("%YR%MN%DY") 2 format is YYMMDD - dates can be 'faked' here   input$=today$+":"+challenge$  offset%=(input$) c offset%>=56 7:"This program will not work correctly with such a long challenge string!":  B we are assuming that the string is fewer than 56 characters!  ` bits%>31 7:"This program will not work correctly to find so many bits of collision!":  blockstart%=0 workreg%=5  work2%=6  count%=4 )Hptr%=10: pointer to the data blocks target%=1 ! mask%=12 " result%=8 # $ ourcode% length% %L%=ourcode%+length% &assemble(bits%) ' A%=W% ( B%=Hblock ) * "Speed test:" +*ș "TaskWindow_TaskInfo" taskwindow% ,% are we running in a taskwindow? -' taskwindow%=0 "Spool "+output$ .* if so redirect screen output to file /speed%=timedhash2 0 1! speed%;" hashes per second" 2*"Checking for ";(bits%);"-bit match" 3 4( estimate$=hours((1<20 looplimit%=1 9 : loop%=1 looplimit% ; $W%=input$ < = T= > ourcode% ? @ T$=hours((-T)/100) A (!tries%)>0 B8 "X-Hashcash: "string(W%)'!tries%;" tries "; C "Actual time ";T$ D E6 "!! Escape pressed - hashcash aborted !!" F/ loop%=looplimit%+1: abort rest of loop G H zero(W%,80*4) I J loop% K L: taskwindow%=0 "Spool":"SetType "+output$+" Text" M close spooled file N O P> string(a%): Read &80-terminated string from work block Q n% R n%=0 64 4 S W%!n%=endianBas(W%!n%) T U' reverse it back so we can read it V n%=a% W X n%+=1 Y ?n%=&80 Z ?n%=13 [=$a% \ ] ^ assemble(bits%) _ pass% `% implement two passes and set up a% values for OPT at the same time b* first pass - enable only range check c i.e. OPT 8 d( second pass - enable error report, e* range check and listing, i.e. OPT 11 f pass% = 8 10 2 g" set P% to start of our code h on each pass i P% = ourcode% j now invoke the assembler k [OPT pass% l1 STMFD R13!,{14} ;store return address m& ; set value of OPT as appropriate n o$; r0 points to start of block W% p.; r1 points to start of Hblock (constants) q rD MOV Hptr%,r1 ;set up Hptr% (cannot pass to R10 from BASIC) sQ MOV workreg%,r0; preserve block pointer (OS_ReadMonotonicTime corrupts) tT;------set up random seed------------------------------------------------------- u v).random ;generate two 32-bit integers w, SWI "OS_ReadMonotonicTime";get seed x MOV r3,r0 y LDR r2,randseed z0 MLA r3,r2,r3,r2 ;random number in r3 { |0 MOV r0,workreg%; restore block pointer } ~T;------do first hash------------------------------------------------------------  . MOV work2%,#(today$+":"+challenge$)  ;STMFD R13!,{r0,r1,r2} ( MOV workreg%,work2%,LSL#3 ;*8 endian ( STR workreg%,[blockstart%,#60] ? ; poke length of string (in bits) into final word of block 2 MOV workreg%,#%10000000;lefthand bit set S STRB workreg%,[blockstart%,work2%];poke in a bit after last byte of string 9 ;remember, strings run from block%?0 to block%?(n-1)  L ;-----convert input----------------------------------------------------  > MOV r7,blockstart% ;point to start of 16 word block  , ADD work2%,r7,#16*4 ;point to end .conv  LDR workreg%,[r7] endian 1 STR workreg%,[r7],#4;store & increment  CMP r7,work2%  Bne conv  ;LDMFD R13!,{r0,r1,r2}  T ;------------------------------------------------------------------------------  (BL hash ;hash date+fluffybunny ! ;target value now in result% ;r0 (blockstart%) preserved T;---------initialise counter and flags------------------------------------------  + MOV count%,#0 ;initialise counter  MOV mask%,#(1<<31) ' MOV mask%,mask%,ASR#(bits%-1) K ;set bits starting from left end according to number required to match  7 target%,result%,mask% ;mask out bottom bits   ;MOV target%,result%  6 MOV work2%,#(today$+":"+challenge$+":")+16 & ;length of full test hash string  " MOV r14,work2%,LSL#3 ;*8 # STR r14,[blockstart%,#60] ? ; poke length of string (in bits) into final word of block A; note - above section moved out of main loop for extra speed   T;=========main test loop========================================================  R;------write counter_hex values as string into hash buffer after challenge---- " .add64 ;get next counter hex  ADDS r3,r3,#1 A ADC r2,r3,r2 ;64bit addition (number held in r2-r3)  STMFD R13!,{0-5}  MOV R1,blockstart% * MOV R0,#(today$+":"+challenge$)  BL reversewrite  TST count%,#&F K SWIeq "OS_ReadEscapeState" ;carry set if Escape condition detected  LDMFD R13!,{0-5} D Bcs escaped ;jump out of loop, acknowledge escape and quit  T;--------hash combined string and check for match with target-------------------  # BL hash ;hash the string @ ;blockstart%,target%,count%,r2-r3(hex regs),mask% preserved $ ;hash value returned in result%  ADD count%,count%,#1 *;check value in result% against target 7 result%,result%,mask% ;mask out bottom bits 5 CMP target%,result% ;check for target found 8 Bne add64 ;if not found try next random number  T;===========tidy up and exit==================================================== .exit L STR count%,tries% ;write out number of tries into buffer at tries% 5 LDMFD R13!,{PC} ;restore return value into PC  T;==========subroutines follow=================================================== T;-------call this if escape detected-------------------------------------------- .escaped  MOV R0,#124 1 SWI "XOS_Byte" ; clear escape condition G MVN count%,#0 ;force count negative to signal aborted attempt 1 B exit ;branch to exit as normal   T;----------main SHA-1 hash routine---------------------------------------------- .hash  STMFD R13!,{r0-r4,r14} 5; > R0 points at endian-converted data (blockend) ; < R8=result (A)  0; R0 is ptr throughout, R7 is an end pointer 3; R10 points to block of 'special values' K1-K4 T; corrupts r1-r6 (A%-E% + TEMP%), R8, R14 (work registers), R11 (special values) ; R12 (mask%) not used  P ADD R7, R0, #80*4 ;point to end of initial 80-word block . ;SWI "OS_BreakAddress" K ADD R0, R0, #16*4 ;we subtract this at the start of 4 ;each loop  '.stepb SUB R0, R0, #64 5 LDMIA R0, {R1-R5} ; -64, -56 O R1, R1, R3 ;1st set - R1 holds t%-64, R3 holds t%-56 O R2, R2, R4 ;2nd set - R2 holds t%-64, R4 holds t%-56 O R3, R3, R5 ;3rd set - R3 holds t%-64, R5 holds t%-56 H ;note R3 is being used by *two* sets simultaneously! < ADD R0,R0,#32 ;advance pointer  0 LDMIA R0, {R4-R6} ; -32 P R1, R1, R4 ;R1,R2,R3 hold running total for sets 1,2,3 M R2, R2, R5 ;R4,R5,R6 hold t%-32 for respective sets $ R3, R3, R6 < ADD R0,R0,#20 ;advance pointer  1 LDMIA R0!, {R4-R6} ; -12 $ R1, R1, R4 $ R2, R2, R5 $ R3, R3, R6 ? ;t%-64 t%-56 t%-32 t%-12 for each set ( MOV R1,R1,ROR#31 ( MOV R2,R2,ROR#31 ( MOV R3,R3,ROR#31 < ;rotate result leftwards by one position ( STMIA R0!, {R1-R3} 5 ;store at t% relative to each set " CMP R0, R7  .check ! Blo stepb Q; t%-12,t%-32,t%-56 and t%-64 together in parallel for consecutive addresses );using 3 pairs of registers at a time   K SUB R0, R7, #80*4;move pointer to start of W% block  > ADD R7, R0, #80 ;end pointer position  .stepc  9 LDMIA Hptr%, {R1-R5} ; R1-5=A-E  ';gollop five registers in from Hptr   * LDR R11, special+0   .stepd A R14, R3, R4 ; R14=work2=C^D < R14, R2, R14 ;B (C^D) D R14, R4, R14 ;D (B (C D)) B ; equivalent to (b% c%) (( b%) d%), one less operation  N ADD R6, R14,R1, ROR#27 ;TEMP=shifted A plus total 7 ADD R6, R6, R5 ;+E  G LDR R14, [R0],#4 ;load from W% block @ ADD R6, R6, R14 ;add to TEMP : ADD R6, R6, R11 ; +K1%  F;Now, instead of rotation... we rotate the sense, unroll the loop, H;and continue. The loop should have run 20 times, so we only have to F;check for termination after the 2nd of the six repeats (3*6+2=20)  0 ; E=D R4=E !0 ; D=C R3=D "D MOV R2, R2, ROR#2 ; R2=C (shift B left 30) #0 ; B=A R1=B $9 ; A=TEMP R6=A, R5=TEMP % &= R14, R2, R3 ; work2=C^D '& R14, R1, R14 (& R14, R3, R14 )J ADD R5, R14, R6, ROR#27 ;TEMP=shifted A plus total *& ADD R5, R5, R4 + ,( LDR R14, [R0],#4 -' ADD R5, R5, R14 .: ADD R5, R5, R11 ; +K1% / 0 ; again 1) MOV R1, R1, ROR#2 2 3M CMP R0, R7 ; completed the 20th run? 4 BCS stepd_20 5 6= R14, R1, R2 ; work2=C^D 7& R14, R6, R14 8& R14, R2, R14 9J ADD R4, R14, R5, ROR#27 ;TEMP=shifted A plus total :& ADD R4, R4, R3 ; <( LDR R14, [R0],#4 =' ADD R4, R4, R14 >: ADD R4, R4, R11 ; +K1% ? @ ; again A) MOV R6, R6, ROR#2 B C= R14, R6, R1 ; work2=C^D D& R14, R5, R14 E& R14, R1, R14 FJ ADD R3, R14, R4, ROR#27 ;TEMP=shifted A plus total G& ADD R3, R3, R2 H I( LDR R14, [R0],#4 J' ADD R3, R3, R14 K: ADD R3, R3, R11 ; +K1% L M ; again N) MOV R5, R5, ROR#2 O P= R14, R5, R6 ; work2=C^D Q& R14, R4, R14 R& R14, R6, R14 S/ ADD R2, R14, R3, ROR#27 T& ADD R2, R2, R1 U V( LDR R14, [R0],#4 W' ADD R2, R2, R14 X: ADD R2, R2, R11 ; +K1% Y Z ; again [) MOV R4, R4, ROR#2 \ ]= R14, R4, R5 ; work2=C^D ^& R14, R3, R14 _& R14, R5, R14 `M ADD R1, R14, R2, ROR#27 ;TEMP=shifted A plus total a& ADD R1, R1, R6 b c( LDR R14, [R0],#4 d' ADD R1, R1, R14 e: ADD R1, R1, R11 ; +K1% f g ; finally h) MOV R3, R3, ROR#2 iI B stepd ; 20th is never here! j*; ---------------------- k l.stepd_20 ; R4=TEMP m ; R3=E n ; R2=D o ; R1=C p ; R6=B q ; R5=A rG ADD R7, R7, #80 ;end of next block sJ LDR R11, special+4 ; next magic constant t u .stepd2 v9 R14, R6, R1 ;B C w= R14, R14, R2 ;B C D xO ADD R4, R14, R5, ROR#27 ;TEMP=shifted A plus total y8 ADD R4, R4, R3 ;+E z {( LDR R14, [R0],#4 |' ADD R4, R4, R14 }' ADD R4, R4, R11 ~ H ; once again, we rotate the sense, not the registers ) MOV R6, R6, ROR#2  % R14, R5, R6 & R14, R14, R1 K ADD R3, R14, R4, ROR#27 ;TEMP=shifted A plus total & ADD R3, R3, R2  ( LDR R14, [R0],#4 ' ADD R3, R3, R14 ' ADD R3, R3, R11   ; once again ) MOV R5, R5, ROR#2   CMP R0, R7  BCS stepd2_20  % R14, R4, R5 & R14, R14, R6 J ADD R2, R14, R3, ROR#27 ;TEMP=shifted A plus total & ADD R2, R2, R1  ( LDR R14, [R0],#4 ' ADD R2, R2, R14 ' ADD R2, R2, R11   ; once again ) MOV R4, R4, ROR#2  % R14, R3, R4 & R14, R14, R5 K ADD R1, R14, R2, ROR#27 ;TEMP=shifted A plus total & ADD R1, R1, R6  ( LDR R14, [R0],#4 ' ADD R1, R1, R14 ' ADD R1, R1, R11   ; once again ) MOV R3, R3, ROR#2  % R14, R2, R3 & R14, R14, R4 J ADD R6, R14, R1, ROR#27 ;TEMP=shifted A plus total & ADD R6, R6, R5  ( LDR R14, [R0],#4 ' ADD R6, R6, R14 ' ADD R6, R6, R11   ; once again ) MOV R2, R2, ROR#2  % R14, R1, R2 & R14, R14, R3 I ADD R5, R14, R6, ROR#27;TEMP=shifted A plus total & ADD R5, R5, R4  ( LDR R14, [R0],#4 ' ADD R5, R5, R14 ' ADD R5, R5, R11   ; finally ) MOV R1, R1, ROR#2 " B stepd2 *; ----------------------  .stepd2_20 ; R2=TEMP  ; R1=E  ; R6=D  ; R5=C  ; R4=B  ; R3=A ' ADD R7, R7, #80 K LDR R11, special+8 ; third magic constant   .stepd3 8 R R14, R5, R6 ;C D = R14, R4, R14 ;B (C D) K R8, R5, R6 ;C D (R8=workreg here) K R R14, R8, R14 ;(B (C D)) (C D) I ;equivalent to (b% c%) (b% d%) (c% d%), one less operation  J ADD R2, R14, R3, ROR#27 ;TEMP=shifted A plus total - ADD R2, R2, R1 ; +E%  I LDR R14, [R0],#4 ;load from pointer and increment ' ADD R2, R2, R14 ' ADD R2, R2, R11  $ ; sense rotation ) MOV R4, R4, ROR#2  & R R14, R4, R5 & R14, R3, R14 C R8, R4, R5 ; R8=workreg here ' R R14, R8, R14 I ADD R1, R14, R2, ROR#27;TEMP=shifted A plus total & ADD R1, R1, R6  ( LDR R14, [R0],#4 ' ADD R1, R1, R14 ' ADD R1, R1, R11  $ ; sense rotation ) MOV R3, R3, ROR#2  CMP R0, R7  BCS stepd3_20  & R R14, R3, R4 & R14, R2, R14 C R8, R3, R4 ; R8=workreg here ' R R14, R8, R14 I ADD R6, R14, R1, ROR#27;TEMP=shifted A plus total & ADD R6, R6, R5  ( LDR R14, [R0],#4 ' ADD R6, R6, R14 ' ADD R6, R6, R11  $ ; sense rotation ) MOV R2, R2, ROR#2  & R R14, R2, R3 & R14, R1, R14 C R8, R2, R3 ; R8=workreg here ' R R14, R8, R14 I ADD R5, R14, R6, ROR#27;TEMP=shifted A plus total & ADD R5, R5, R4  ( LDR R14, [R0],#4 ' ADD R5, R5, R14 ' ADD R5, R5, R11  $ ; sense rotation ) MOV R1, R1, ROR#2   & R R14, R1, R2  & R14, R6, R14  C R8, R1, R2 ; R8=workreg here  ' R R14, R8, R14  I ADD R4, R14, R5, ROR#27;TEMP=shifted A plus total & ADD R4, R4, R3  ( LDR R14, [R0],#4 ' ADD R4, R4, R14 ' ADD R4, R4, R11  $ ; sense rotation ) MOV R6, R6, ROR#2  * MOV R3, R4, ROR#27 & R R14, R6, R1 & R14, R5, R14 C R8, R6, R1 ; R8=workreg here ' R R14, R8, R14 I ADD R3, R14, R4, ROR#27;TEMP=shifted A plus total & ADD R3, R3, R2  ( LDR R14, [R0],#4  ' ADD R3, R3, R14 !' ADD R3, R3, R11 " #$ ; sense rotation $) MOV R5, R5, ROR#2 %" B stepd3 &*; ---------------------- ' (.stepd3_20 ; R6=TEMP ) ; R1-R5=A-E *' ADD R7, R7, #80 +J LDR R11, special+12 ; fourth magic value , - .stepd4 .% R14, R2, R3 /6 R14, R14, R4 ;(b% c% d%) 0J ADD R6, R14, R1, ROR#27 ;TEMP=shifted A plus total 1- ADD R6, R6, R5 ; +E% 2 3( LDR R14, [R0],#4 4' ADD R6, R6, R14 5' ADD R6, R6, R11 6 7 ; rotate 8) MOV R2, R2, ROR#2 9 :% R14, R1, R2 ;& R14, R14, R3  ?( LDR R14, [R0],#4 @' ADD R5, R5, R14 A' ADD R5, R5, R11 B C ; rotate D) MOV R1, R1, ROR#2 E CMP R0, R7 F BCS stepd4_20 G H% R14, R6, R1 I& R14, R14, R2 JI ADD R4, R14, R5, ROR#27;TEMP=shifted A plus total K& ADD R4, R4, R3 L M( LDR R14, [R0],#4 N' ADD R4, R4, R14 O' ADD R4, R4, R11 P Q ; rotate R) MOV R6, R6, ROR#2 S T% R14, R5, R6 U& R14, R14, R1 VI ADD R3, R14, R4, ROR#27;TEMP=shifted A plus total W& ADD R3, R3, R2 X Y( LDR R14, [R0],#4 Z' ADD R3, R3, R14 [' ADD R3, R3, R11 \ ] ; rotate ^) MOV R5, R5, ROR#2 _ `% R14, R4, R5 a& R14, R14, R6 bI ADD R2, R14, R3, ROR#27;TEMP=shifted A plus total c& ADD R2, R2, R1 d e( LDR R14, [R0],#4 f' ADD R2, R2, R14 g' ADD R2, R2, R11 h i ; rotate j) MOV R4, R4, ROR#2 k l% R14, R3, R4 m& R14, R14, R5 nI ADD R1, R14, R2, ROR#27;TEMP=shifted A plus total o& ADD R1, R1, R6 p q( LDR R14, [R0],#4 r' ADD R1, R1, R14 s' ADD R1, R1, R11 t u ; rotate v) MOV R3, R3, ROR#2 w" B stepd4 x*; ---------------------- y z.stepd4_20 ; R4=TEMP { ; R5=A | }( LDR R14, [Hptr%] ~J ADD result%, R5, R14 ; result=A+[Hptr]  ; Result is in result%  < LDMFD R13!,{r0-r4,PC} ;restore return value into PC, ) ;block pointer,hex regs,target value   T;=========write little-endian hex, non word-aligned!============================  $; > R0=length of original string ; R1=buffer ?; R2-R3=number to append (top nibble of R3 written first) ; < R0-R5 corrupted  @.reversewrite R4, R0, #3 ; byte offset R ;preserve right-hand bits of R0 in R4 A BIC R0, R0, #3 ; word offset P ADD R1, R1, R0 ; ptr to word containing the I ; first byte to write K R0, R4, #3 ; REVERSE the byte offset  ; MOV R4, #58 ; colon F STRB R4, [R1,R0] ; append to string O SUBS R0, R0, #1 ; if we drop out the bottom J ADDMI R1, R1, #4 ; go to the next word K MOVMI R0, #3 ; and start at the top  E MOV R5, #15 ; 16 digits to do C.digits MOV R4, R3, LSR#28 ; get a nibble # CMP R4, #10 ' ADDCS R4, R4, #87 D ADDCC R4, R4, #48 ; convert to hex  ; STRB R4, [R1,R0] ; store J SUBS R0, R0, #1 ; advance to next byte & ADDMI R1, R1, #4 " MOVMI R0, #3  L MOV R3, R3, LSL#4 ; rotate 64bit number by < R R3, R3, R2, LSR#28 ; 4 bits ) MOV R2, R2, LSL#4  @ SUBS R5, R5, #1 ; next digit " BPL digits  E MOV R4, #%10000000 ;lefthand bit set F STRB R4, [R0,R1] ; terminate string # MOV PC, R14   T;========speed test routine - count% hashes===================================== .speedstart  STMFD R13!,{r14}  .timer  BL hash  STMFD R13!,{0-2} I ADDS count%,count%,#0 ;clear carry flag returned by hash routine  ; (not needed in real loop)  TST count%,#&F 9 SWIeq "OS_ReadEscapeState" ;scan for escape key  LDMFD R13!,{0-2} > Bcs escaped ;acknowledge and exit via main routine  SUBS count%,count%,#1  Bne timer   LDMFD R13!,{pc} T;================debugging - call endian from BASIC=============================  .switch  endian MOV R0,R5 ,MOV R15, R14 ;exit routine T;==========data blocks follow===================================================   .randseed EQUD 1664525  .Hblock EQUD &67452301 EQUD &EFCDAB89 EQUD &98BADCFE EQUD &10325476 EQUD &C3D2E1F0  .special ;'magic' constants  EQUD &5A827999  EQUD &6ED9EBA1  EQUD &8F1BBCDC  EQUD &CA62C1D6   .tries%  EQUD 0  ALIGN  ] $ we have now left the assembler  pass% # OSCLI"BreakSet "+STR$~(check)     zero(block%,len%)  n%  n%=0 len% 4  block%!n%=0  n%     date(format$)  r%  ?UTC%=3 ș"OS_Word",14,UTC% " current time in centiseconds Kș"OS_ConvertDateAndTime",UTC%,dateblock%,&24,format$+(0) ,r%:?r%=13 =$dateblock% ' Time in GMT (even for foreigners)   hours(sec%)  a$,R% R%=sec% 60:sec%=sec% 60 a$=(R%)+" sec" R%=sec% 60:sec%=sec% 60 )a$=(sec%)+" hours "+(R%)+" min "+a$ =a$   endian . macro this in instead of branching to it [OPT pass% T;====reverse order of bytes in a register == big/little-endian================== (; data should be in r5, output in r5 ; corrupts R1 C r1,r5,r5,ROR #16 ; (aEORc)(bEORd)(aEORc)(bEORd) E BIC r1,r1,#&FF0000 ; (aEORc) 0 (aEORc)(bEORd) B MOV r5,r5,ROR #8 ; d a b c @ r5,r5,r1,LSR #8 ; d c b a  ] =0   endianBas(int%)  F%=int%  =(switch)     timedhash2   B%=Hblock  A%=W%  E%=1600: count%.   E%=E%*2 0 put T=Time as last statement before call.  T=  speedstart  T=(-T)/100  T>2 - T;" seconds to complete ";E%;" hashes"  =E%/T