3 # Date: 2020-02-18T20:06Z
5 # Author: Steven Baltakatei Sandoval (baltakatei.com)
7 # License: This bash script, 'bkpoe', is licensed under GPLv3 or
8 # later by Steven Baltakatei Sandoval:
10 # 'bkpoe', a file system proof of existence generator
11 # Copyright (C) 2020 Steven Baltakatei Sandoval (baltakatei.com)
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # A copy of the GNU General Public License may be found at
24 # <https://www.gnu.org/licenses/>.
26 # Description: This is a simple script that uses GNU Coreutils
27 # OpenTimestamps to generate a proof that permits a user to prove the
28 # existence of any individual file within a directory. A proof
31 # - An OpenTimestamps 'ots' file (if '-t' option provided)
33 # - Public proof: text file(s) containing a list of salted file
36 # - Private proof: text file(s) containing:
38 # - A list of unsalted file hashes
40 # - A list of nonces used to generate the list of salted file hashes.
42 # - The list of salted file hashes
44 # - File attributes (size, relative path)
46 # The proof is produced by performing the following steps:
48 # - Use 'find' and GNU Coreutils digest algorithms to create
49 # numbered lists of file digests at specified directories.
51 # - Save these numbered lists of digests into text files within a
54 # - If more than one specified directory is provided also process
55 # the files within the proof directory.
57 # - Use OpenTimestamps to create an 'ots' timestamp file
58 # for the final digest list file. (if '-t' option provided).
60 # - Use OpenTimestamps to upgrade each 'ots' timestamp file after a
61 # delay. (if '-w' option provided).
63 # Dependencies: coreutils, ots. See end of file
67 # - GNU/Linux Debian 10
70 # === VARIABLE INITIALIZATION AND FUNCTION DEFINITIONS ===
72 PATH
="/usr/local/bin/:$PATH" # Add default OpenTimestamps path to PATH (necessary if cron used to call this script)
73 scriptHostname
=$
(hostname
) # Save hostname of system running this script.
74 OTS_TIMEOUT
="6h" # maximum time to run `ots --wait` (used via `timeout` command)
76 # Declare array for storing directories whose contents will be hashed.
77 declare -a directoriesToProcess
79 # Declare arrays for storing file paths, file index, file hashes, file
80 # hash nonces, the salted hashes, file sizes, file paths, file
81 # modification times, and proof file paths.
82 declare -a filePathsFull
# array
83 declare -a filePathsIndex
# array
84 declare -a fileHashes
# array
85 declare -a fileHashNonces
# array
86 declare -a fileHashesSalted
# array
87 declare -a fileSizes
# array
88 declare -a fileModTime
# array
89 declare -g PROOF_DIRECTORY
# global
90 declare -ag prvProofPaths
# array, global
91 declare -ag pubProofPaths
# array, global
93 # Define 'echoerr' function which outputs text to stderr
94 # Note: function copied from https://stackoverflow.com/a/2990533
99 # Define script version.
100 SCRIPT_VERSION
="bkpoe 0.1.0"
103 runDate
=$
(date +%Y
%m
%dT
%H
%M
%S
%z
)
104 sleep 1 # Space out program run times by at least one second.
105 ###echoerr "DEBUG: Current date is:"$runDate
107 # Save working directory
109 ###echoerr "DEBUG: Current working directory is:"$runPath
111 # Define proof directory basename
112 PROOF_DIRECTORY_BASE
="$runDate""..""$scriptHostname""_bkpoe_proofs"
114 # Define default proof directory
115 PROOF_DIRECTORY
="$runPath"/"$PROOF_DIRECTORY_BASE"
117 # Print information on how to use this bash script.
121 echo " bkpoe [ options ] DIRECTORIES"
125 echo " Display help information."
127 echo " -r, --recursive"
128 echo " Perform recursive hashing of directories."
130 echo " -v, --verbose"
131 echo " Display debugging info."
133 echo " -a, --algorithm [ algo ]"
134 echo " Specify GNU Coreutils hash command. Options include: "
135 echo " b2sum md5sum sha1sum sha224sum sha256sum sha384sum "
138 echo " -m, --message [ string ]"
139 echo " Specify message to be included in filehash files."
141 echo " -d, --delimiter [ string ]"
142 echo " Specify character to serve as delimiter in proof files. "
143 echo " (Default: ,) "
144 echo " -t, --timestamp"
145 echo " Create openTimestmaps proof file."
147 echo " Pass '--wait' option to OpenTimestamps to 'wait "
148 echo " until a complete timestamp committed in the "
149 echo " Bitcoin blockchain is available instead of "
150 echo " returning immediately. "
152 echo " Specify output directory to save proof. Default is working"
153 echo " directory where script was run."
157 # Check that a string matches one of GNU Coreutils hash commands.
158 isValidDigestAlgo
() {
159 # Syntax: isValidDigestAlgo [string]
160 # Description: Returns 1 if [string] is a valid GNU Coreutils hash function command. Returns 0 otherwise.
162 # Exit if more than one argument provided.
163 ###echoerr "DEBUG: Arguments provided to isValidDigestAlgo() are:$@"
164 if [[ $# -ne 1 ]]; then
165 echoerr
"ERROR: Illegal number of digest arguments provided."
169 # Exit if argument contains non-alphanumeric characters
170 if [[ "$@" =~
[^a-zA-Z0-9$
] ]]; then
171 echoerr
"ERROR: Illegal characters for digest argument provided."
175 local input
=$1 #Save first argument provided to function as input for further processing.
177 ###echoerr "DEBUG: Starting function to check validity of string as valid digest command."
179 # Define array of valid hash algorithm commands present in by GNU coreutils package. List is current as of Debian Buster. List taken from https://packages.debian.org/buster/amd64/coreutils/filelist ).
180 local gnuCoreutilsAlgos
=(b2sum
md5sum sha1sum sha224sum sha256sum sha384sum sha512sum
)
181 ###echoerr "DEBUG:Contents of \$gnuCoreutilsAlgos array is:${gnuCoreutilsAlgos[@]}"
183 # Make input string lowercase
184 #input=${input,,} # Make all letters in $1 lowercase (see https://stackoverflow.com/questions/2264428/how-to-convert-a-string-to-lower-case-in-bash ) and save as $input
185 ###echoerr "DEBUG: User-specified hash algorithm is:$input"
187 # Return true (0) if $gnuCoreutilsAlgos array contains string $input (see https://unix.stackexchange.com/a/177139 )
188 for item
in "${gnuCoreutilsAlgos[@]}"; do
189 ###echoerr "DEBUG: \$item is:$item"
190 ###echoerr "DEBUG: \$input is:$input"
191 if [ "$input" == "$item" ]; then
192 ###echoerr "DEBUG: $input present in \$gnuCoreutilsAlgos array."
197 # Return false (1) otherwise.
198 echoerr
"DEBUG: $input not recognized as valid hash algorithm name."
203 # Syntax: writeProofs [ loopIndex ] [ dirPath ]
205 # Description: Construct private and public proofs for files
206 # contained within [ dirPath ]. Save file with date, [ loopIndex ],
207 # and base directory name within filename.
209 # Required variables:
210 # - $runDate: used in creating proof directory
211 # - $PROOF_DIRECTORY: used as directory where proof files are saved
212 # - $OPTION_ALGORITHM: used to decide which hash algorithm to use
213 # - $OPTION_DELIMITER: used to determine which char to use as a delimiter in proof files
214 # - $OPTION_MESSAGE: used to determine message included in proof files
215 # - $OPTION_RECURSIVE: used to determine if files within subdirectories under [ dir Path ] directory should be included
217 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG:This is the writeProofs() function. Provided arguments are:$@"
219 # Determine scope of 'find' file search (recursive search or not)
220 if [[ $OPTION_RECURSIVE == "true" ]]; then
221 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: Performing recursive file search on directory $directoryToHash"
222 findMaxDepthOption
="" # Do not specify -maxdepth option for find (causes 'find' to include all files recursively within [ dirPath ]
224 findMaxDepthOption
="-maxdepth 1" # Specify "-maxdepth 1 " as option for 'find' so only files immediately within [ dirPath ] directory are included (no subdirectories).
227 # Save current date of loop
228 proofLoopRunDate
=$
(date +%Y
%m
%dT
%H
%M
%S
%z
)
229 ###echoerr "DEBUG: Date and time of current loop is:"$proofLoopRunDate
240 # Pass first argument provided to function as directory index.
243 # Pass second argument as directory to process
245 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: Valid directory argument provided to writeProofs() function."
246 local directoryToHash
=$2
247 ## local directoryToHash=$(readlink -f $2) # Use absolutely full path (not used for privacy).
249 echoerr
"ERROR: Invalid directory argument provided to writeProofs() function."
252 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG:Processing directory:"$directoryToHash
253 local directoryToHashShort
="$(basename "$directoryToHash")"
254 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG:Directory basename:"$directoryToHashShort
256 # Populate filePathsFull array with file paths from 'find' results (see https://stackoverflow.com/a/54561526 ).
257 # Note: For creating array from output of find via readarray, see https://unix.stackexchange.com/a/263885
258 # Note: For sorting output of find, see https://unix.stackexchange.com/a/34328
259 readarray
-d '' filePathsFull
< <(find $directoryToHash $findMaxDepthOption -readable -type f
-print0 |
sort -z) # Populate filePathsFull array with sorted list of files present in $directoryToHash (and subdirectories if $OPTION_RECURSIVE set to 'true'.
260 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: The ${#filePathsFull[@]} elements of filePathsFull array are:${filePathsFull[@]}" # Show array length (see https://www.cyberciti.biz/faq/finding-bash-shell-array-length-elements/ )
262 # Construct index array from filePathsFull array for the for loops used to write proofs.
263 filePathsIndex
=(${!filePathsFull[@]})
264 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: The ${#filePathsIndex[@]} elements of filePathsIndex array are:${filePathsIndex[@]}"
266 # Populate fileHashes with hashes of files listed in filePathsFull array
267 # Iterate through all elements of filePathsFull array using an integer index. (see https://stackoverflow.com/a/6723516 )
268 for i
in "${!filePathsIndex[@]}"; do
269 ###[[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG:The $i th element of the filePathsFull array is:${filePathsFull[i]}"
270 fileHashes
[i
]=$
($OPTION_ALGORITHM "${filePathsFull[i]}" |
awk '{ print $1 }') # Get hash value from GNU Coreutils hash command (see https://stackoverflow.com/a/3679803 )
272 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: The ${#fileHashes[@]} elements of fileHashes array are:${fileHashes[@]}"
274 # Populate fileHashNonces array with same number of elements as filePathsFull array. Each element is a random hexadecimal nonce of length matching OPTION_ALGORITHM. Hex nonce generated from 64-byte (512-bits) block from /dev/urandom.
275 for i
in "${!filePathsIndex[@]}"; do
276 fileHashNonces
[i
]=$
(dd bs
=64 count
=1 if=/dev
/urandom
2>/dev
/null |
$OPTION_ALGORITHM |
awk '{ print $1 }')
278 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: The ${#fileHashNonces[@]} elements of fileHashNonces array are:${fileHashNonces[@]}"
280 # Populate fileHashesSalted array with salted digest for each file hash corresponding nonce.
281 for i
in "${!filePathsIndex[@]}"; do
282 fileHashesSalted
[i
]=$
(echo -n "${fileHashes[i]}${fileHashNonces[i]}" | $OPTION_ALGORITHM | awk '{ print $1 }')
284 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: The
${#fileHashesSalted[@]} elements of fileHashesSalted array are
:${fileHashesSalted[@]}"
286 # Populate fileSizes array with file sizes (in bytes).
287 for i in "${!filePathsIndex[@]}"; do
288 fileSizes[i]=$(wc -c "${filePathsFull[i]}" | awk '{ print $1 }')
290 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: The
${#fileSizes[@]} elements of fileSizes array are
:${fileSizes[@]}"
292 # Populate fileModTime array with file modification times (in ISO-8601 format)
293 for i in "${!filePathsIndex[@]}"; do
294 fileModTime[i]=$(date --iso-8601=seconds -r "${filePathsFull[i]}" | awk '{ print $1 }')
296 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: The
${#fileModTime[@]} elements of fileModTime array are
:${fileModTime[@]}"
298 # ==== GENERATE PROOF FILES FROM HASH LIST(S) ====
300 # Define output file names and paths.
301 local PROOF_PRIVATE_FILENAME=$proofLoopRunDate"_
"$loopIndex"_prv..
"$scriptHostname"_
"$directoryToHashShort"_
"$OPTION_ALGORITHM"_proof_private.txt
"
302 local PROOF_PUBLIC_FILENAME=$proofLoopRunDate"_
"$loopIndex"_pub..
"$scriptHostname"_
"$directoryToHashShort"_
"$OPTION_ALGORITHM"_proof_public.txt
"
303 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: \
$PROOF_PRIVATE_FILENAME is
$PROOF_PRIVATE_FILENAME"
304 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: \
$PROOF_PUBLIC_FILENAME is
$PROOF_PUBLIC_FILENAME"
305 PROOF_PRIVATE_FILEPATH="$PROOF_DIRECTORY"/"$PROOF_PRIVATE_FILENAME"
306 PROOF_PUBLIC_FILEPATH="$PROOF_DIRECTORY"/"$PROOF_PUBLIC_FILENAME"
308 # Create private proof and public proof files.
309 touch "$PROOF_PRIVATE_FILEPATH"
310 touch "$PROOF_PUBLIC_FILEPATH"
312 # Abbreviate OPTION_DELIMITER to local variable DELIM
313 local DELIM=$OPTION_DELIMITER
315 # Define private proof first-line headers (date, version, algorithm, message)
316 local PROOF_PRIVATE_HEADER1="$proofLoopRunDate$DELIM$SCRIPT_VERSION$DELIM$OPTION_ALGORITHM$DELIM$OPTION_MESSAGE"
317 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: \
$PROOF_PRIVATE_HEADER1 is
$PROOF_PRIVATE_HEADER1"
318 # Define public proof first-line headers (date, version, algorithm, message)
319 local PROOF_PUBLIC_HEADER1="$proofLoopRunDate$DELIM$SCRIPT_VERSION$DELIM$OPTION_ALGORITHM$DELIM$OPTION_MESSAGE"
320 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: \
$PROOF_PUBLIC_HEADER1 is
$PROOF_PUBLIC_HEADER1"
322 # Define private proof second-line column labels (index, digest, nonce, salted digest, mdate, size, filepath)
323 local PROOF_PRIVATE_HEADER2="index
"$DELIM"digest
"$DELIM"nonce
"$DELIM"digest_salted
"$DELIM"mdate
"$DELIM"size_bytes
"$DELIM"file_path
"
324 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: \
$PROOF_PRIVATE_HEADER2 is
$PROOF_PRIVATE_HEADER2"
325 # Define public proof second-line column labels (index, salted digest)
326 local PROOF_PUBLIC_HEADER2="index
"$DELIM"digest_salted
"
327 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: \
$PROOF_PUBLIC_HEADER2 is
$PROOF_PUBLIC_HEADER2"
329 # Write headers to proof files
330 echo $PROOF_PRIVATE_HEADER1 >> "$PROOF_PRIVATE_FILEPATH"
331 echo $PROOF_PRIVATE_HEADER2 >> "$PROOF_PRIVATE_FILEPATH"
332 echo $PROOF_PUBLIC_HEADER1 >> "$PROOF_PUBLIC_FILEPATH"
333 echo $PROOF_PUBLIC_HEADER2 >> "$PROOF_PUBLIC_FILEPATH"
335 # Loop to append array contents.
336 for i in "${!filePathsIndex[@]}"; do
337 # Append to private proof: filePathsIndex[i],fileHashes[i],fileHashNonces[i],fileHashesSalted[i],fileModTime[i],fileSizes[i],filePathsFull[i]
338 ###[[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: Writing line to
$PROOF_PRIVATE_FILEPATH"
339 echo ${filePathsIndex[i]}$DELIM${fileHashes[i]}$DELIM${fileHashNonces[i]}$DELIM${fileHashesSalted[i]}$DELIM${fileModTime[i]}$DELIM${fileSizes[i]}$DELIM${filePathsFull[i]} >> "$PROOF_PRIVATE_FILEPATH"
340 # Append to public proof: filePathsIndex[i],fileHashesSalted[i]
341 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: Writing line to
$PROOF_PUBLIC_FILEPATH"
342 echo ${filePathsIndex[i]}$DELIM${fileHashesSalted[i]} >> $PROOF_PUBLIC_FILEPATH
345 # Save file paths to global variables prvProofPaths and pubProofPaths to allow other functions to manipulate files.
346 prvProofPaths+=($PROOF_PRIVATE_FILEPATH)
347 pubProofPaths+=($PROOF_PUBLIC_FILEPATH)
348 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: The
${#prvProofPaths[@]} elements of prvProofPaths array are
:${prvProofPaths[@]}"
349 [[ $OPTION_VERBOSE == "true
" ]] && echoerr "DEBUG
: The
${#pubProofPaths[@]} elements of pubProofPaths array are
:${pubProofPaths[@]}"
362 # === INPUT PROCESSING ===
363 # Check for presence of options.
364 ###echoerr "DEBUG
: === INPUT PROCESSING START
==="
366 # Process initial option arguments (see https://jonalmeida.com/posts/2013/05/26/different-ways-to-implement-flags-in-bash/ and https://likegeeks.com/linux-bash-scripting-awesome-guide-part3/ )
367 while [ ! $# -eq 0 ] # While number of arguments ($#) is not (!) equal to (-eq) zero (0).
369 case "$1" in # Check first of remaining arguments to see if it matches one of strings below.
371 # Code to run if $1 matched "--help" or "-h":
372 ###echoerr "DEBUG
: This is the
help information.
"
373 usage # Run usage function to display helpful info to user.
377 # Code to run if $1 matched "--recursive" or "-r":
378 ###echoerr "DEBUG
: Recursive option activated.
"
379 OPTION_RECURSIVE="true
"
382 # Code to run if $1 matched "--verbose" or "-v":
383 ###echoerr "DEBUG
: Verbose option activated.
"
384 OPTION_VERBOSE="true
"
387 # Code to run if $1 matched "--algorithm" or "-a":
389 ###echoerr "DEBUG
: Selecting
hash algorithm.
"
390 # Check that OPTION_ALGORITHM variable hasn't already been set.
391 # Check that OPTION_ALGORITHM contains only alphanumeric characters.
392 # Check that argument following '-a' or '--algorithm' ($2) is valid algorithm.
393 if [[ ! -v OPTION_ALGORITHM ]] && [[ ! "$2" =~ [^a-zA-Z0-9$] ]] && isValidDigestAlgo $2; then
394 ###echoerr "DEBUG
: Valid
hash algorithm provided.
"
395 OPTION_ALGORITHM=$2 # Save argument following '-a' or '--algorithm' to $OPTION_ALGORITHM
396 shift # Remove an additional argument so the additional algorithm argument $2 is properly removed at the end of the while loop.
398 echoerr "ERROR
: Invalid
hash algorithm argument provided
:""$2"
403 # Code to run if $1 matched "--message" or "-m":
404 ###echoerr "DEBUG
: Specifying header message.
"
405 # Check that OPTION_MESSAGE variable hasn't already been set.
406 if [[ ! -v OPTION_MESSAGE ]]; then
407 OPTION_MESSAGE=$2 # Save argument following '-m' or '--message' to $OPTION_MESSAGE
408 shift # Remove an additional argument so the additional algorithm argument $2 is properly removed at the end of the while loop.
412 # Code to run if $1 matched "--delimiter" or "-d":
413 ###echoerr "DEBUG
: Specifying delimiter.
"
414 # Check that OPTION_DELIMITER variable hasn't already been set.
415 if [[ ! -v OPTION_DELIMITER ]]; then
416 OPTION_DELIMITER=$2 # Save argument following '-d' or '--delimiter' to $OPTION_DELIMITER
417 shift # Remove an additional argument so the additional delimiter argument $2 is properly removed at the end of the while loop.
421 # Code to run if $1 matched "--timestamp" or "-t":
422 echoerr "DEBUG
: OpenTimestamps option selected.
"
423 # Check that ots command exists. (see https://stackoverflow.com/a/677212 )
424 if command -v ots >/dev/null 2>&1 ; then
425 echoerr "DEBUG
: OpenTimestampscommand
('ots') detected.
"
426 OPTION_OPENTIMESTAMPS="true
"
428 echo >&2 "I require the ots
(OpenTimestamps
) command but it
's not installed. Aborting."
433 # Code to run if $1 matched "--wait" or "-w":
434 echoerr "DEBUG: OpenTimestamps '--wait' option selected."
435 OPTION_OPENTIMESTAMPS_WAIT="true"
438 # Code to run if $1 matched "--output" or "-o":
439 # Check that argument $2 is a directory.
441 echoerr "DEBUG: Specified ""$PROOF_DIRECTORY_BASE"" in ""$2"" as directory for saving proof files."
442 PROOF_DIRECTORY="$2"/"$PROOF_DIRECTORY_BASE"
443 echoerr "DEBUG: Proof Directory is:""$PROOF_DIRECTORY"
444 shift # Remove an additional argument so the additional delimiter argument $2 is properly removed at the end of the while loop.
446 echoerr "ERROR: Invalid argument provided as output directory for proof files:""$2"
451 # Code to run if $1 isn't any of the above cases
:
452 # Check that remaining argument is a directory.
454 ###echoerr "DEBUG: Remaining argument is directory. Adding to \$directoriesToProcess array."
455 # Add directory argument $1 to directory array
456 directoriesToProcess
+=($1) # (see https://stackoverflow.com/a/1951523 )
458 echoerr
"ERROR: Remaining argument is not a directory or valid option. Exiting."
463 shift # Remove first argument ($1) so remaining arguments can be processed on next loop.
466 # If OPTION_ALGORITHM is not set then set it to 'sha256sum' by default.
467 if [[ ! -v OPTION_ALGORITHM
]]; then
468 echoerr
"DEBUG: No hash algorithm set. Defaulting to sha256sum."
469 OPTION_ALGORITHM
="sha256sum"
472 # If OPTION_DELIMITER is not set then set it to ',' by default.
473 if [[ ! -v OPTION_DELIMITER
]]; then
474 ###echoerr "DEBUG: No delimiter set. Defaulting to ','."
478 # Exit program if directoriesToProcess array is empty.
479 if [[ ${#directoriesToProcess[@]} -eq 0 ]]; then
480 echoerr
"ERROR: No valid directory specified."
484 ###echoerr "DEBUG: === INPUT PROCESSING END ==="
486 # === MAIN PROGRAM ===
487 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: === MAIN PROGRAM START ==="
488 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: Verbose option active."
489 [[ $OPTION_VERBOSE == "true" ]] && [[ $OPTION_RECURSIVE == "true" ]] && echoerr
"DEBUG: Recursive option active."
490 [[ $OPTION_VERBOSE == "true" ]] && [[ -v OPTION_ALGORITHM
]] && echoerr
"DEBUG: Selected Hash algorithm is:""$OPTION_ALGORITHM"
491 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: Directories to process are: ${directoriesToProcess[@]}"
492 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: Message to add to proof is: $OPTION_MESSAGE"
493 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: Proof directory is:""$PROOF_DIRECTORY"
494 [[ $OPTION_VERBOSE == "true" ]] && [[ $OPTION_OPENTIMESTAMPS == "true" ]] && echoerr
"DEBUG: OpenTimestamps option active."
495 [[ $OPTION_VERBOSE == "true" ]] && [[ $OPTION_OPENTIMESTAMPS_WAIT == "true" ]] && echoerr
"DEBUG: OpenTimestamps '--wait' option selected."
496 # Create proof directory
497 mkdir
"$PROOF_DIRECTORY"
498 echoerr
"Proof directory created at:""$PROOF_DIRECTORY"
501 # declare -a filePathsFull # array
502 # declare -a filePathsIndex # array
503 # declare -a fileHashes # array
504 # declare -a fileHashNonces # array
505 # declare -a fileHashesSalted # array
506 # declare -a fileSizes # array
507 # declare -a fileModTime # array
508 # declare -ag prvProofPaths # array, global
509 # declare -ag pubProofPaths # array, global
511 # ==== GENERATE HASH LIST(S) ====
512 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: ==== GENERATING HASH LIST(S) ===="
514 # Assemble and write proofs to disk for all specified directories.
516 # Generate proofs from all provided directories.
517 for j
in "${!directoriesToProcess[@]}"; do
518 writeProofs
$j "${directoriesToProcess[j]}"
521 # Decide if superproof is required.
522 if [[ ${#directoriesToProcess[@]} -eq 1 ]]; then
523 # If only one directory provided then no further proofs required.
524 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: Only 1 directory processed. No superproof required."
525 elif [[ ${#directoriesToProcess[@]} -gt 1 ]]; then
526 # If multiple directories provided then create superproof from files in PROOF_DIRECTORY.
527 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: More than 1 directory processed. Proceeding to generate superproof from PROOF_DIRECTORY."
528 writeProofs
"S" $PROOF_DIRECTORY # Create proof using proof files which are stored in PROOF_DIRECTORY
531 # ==== GENERATE HASH LIST(S) REPORT =====
532 echoerr
"Proof files written to $PROOF_DIRECTORY directory:"
533 for i
in ${!prvProofPaths[@]}; do
534 echo $
(basename "${prvProofPaths[i]}")
535 echo $
(basename "${pubProofPaths[i]}")
538 # ==== GENERATE OPENTIMESTAMPS TIMESTAMP FILE(S) ====
540 # Determine if OpenTimestamp actions to be performed.
541 if [[ $OPTION_OPENTIMESTAMPS == "true" ]]; then
542 # Identify target for OpenTimeStamps script.
543 otsTarget
=${pubProofPaths[-1]} # Select final element in pubProofPaths array as target for 'ots' command.
544 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: otsTarget is:""$otsTarget"
546 # Set wait option as determined by OPTION_OPENTIMESTAMPS_WAIT variable. Set timeout length.
547 if [[ $OPTION_OPENTIMESTAMPS_WAIT == "true" ]]; then
548 otsWaitOption
="--wait" # Specify '--wait' option for ots command.
549 otsWaitTimeCmd
="timeout ""$OTS_TIMEOUT" # Specify timeout length (6 hours) for ots command.
550 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: OpenTimestamps '--wait' option active."
554 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: OpenTimestamps '--wait' option not active."
557 # Generate OpenTimeStamps proof file.
558 pushd "$PROOF_DIRECTORY" 1>/dev
/null
2>/dev
/null
# temporarily change working dir to PROOF_DIRECTORY, suppress stdout and stderr
559 $otsWaitTimeCmd ots
$otsWaitOption stamp
$otsTarget
560 echoerr
"OpenTimestamps operation successful! $otsTarget created."
561 echoerr
"Use 'ots info [ filename ]' for more info."
562 echoerr
"Use 'ots upgrade' if '--wait' option not used to make .ots file independently-verifiable."
563 echoerr
"See https://opentimestamps.org/ for more information."
564 popd 1>/dev
/null
2>/dev
/null
# reverse temporary change to working dir, suppress stdout and stderr
569 [[ $OPTION_VERBOSE == "true" ]] && echoerr
"DEBUG: === MAIN PROGRAM END ==="
570 # Exit program successfully.
576 # - bash, find, sort, echo, mkdir, basename, awk, wc, date, dd,
577 # readlink, touch, ots,
579 # - GNU bash, version 5.0.3(1)-release (x86_64-pc-linux-gnu)
580 # Copyright (C) 2019 Free Software Foundation, Inc.
581 # License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
582 # This is free software; you are free to change and redistribute it.
583 # There is NO WARRANTY, to the extent permitted by law.
585 # - find (GNU findutils) 4.6.0.225-235f
586 # Copyright (C) 2019 Free Software Foundation, Inc.
587 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
588 # This is free software: you are free to change and redistribute it.
589 # There is NO WARRANTY, to the extent permitted by law.
590 # Written by Eric B. Decker, James Youngman, and Kevin Dalley.
591 # Features enabled: D_TYPE O_NOFOLLOW(enabled) LEAF_OPTIMISATION FTS(FTS_CWDFD) CBO(level=2)
593 # - sort (GNU coreutils) 8.30
594 # Copyright (C) 2018 Free Software Foundation, Inc.
595 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
596 # This is free software: you are free to change and redistribute it.
597 # There is NO WARRANTY, to the extent permitted by law.
598 # Written by Mike Haertel and Paul Eggert.
600 # - echo (GNU coreutils) 8.30
601 # Copyright (C) 2018 Free Software Foundation, Inc.
602 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
603 # This is free software: you are free to change and redistribute it.
604 # There is NO WARRANTY, to the extent permitted by law.
605 # Written by Brian Fox and Chet Ramey.
607 # - mkdir (GNU coreutils) 8.30
608 # Copyright (C) 2018 Free Software Foundation, Inc.
609 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
610 # This is free software: you are free to change and redistribute it.
611 # There is NO WARRANTY, to the extent permitted by law.
612 # Written by David MacKenzie.
614 # - basename (GNU coreutils) 8.30
615 # Copyright (C) 2018 Free Software Foundation, Inc.
616 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
617 # This is free software: you are free to change and redistribute it.
618 # There is NO WARRANTY, to the extent permitted by law.
619 # Written by David MacKenzie.
621 # - GNU Awk 4.2.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.1.2)
622 # Copyright (C) 1989, 1991-2018 Free Software Foundation.
623 # This program is free software; you can redistribute it and/or modify
624 # it under the terms of the GNU General Public License as published by
625 # the Free Software Foundation; either version 3 of the License, or
626 # (at your option) any later version.
627 # This program is distributed in the hope that it will be useful,
628 # but WITHOUT ANY WARRANTY; without even the implied warranty of
629 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
630 # GNU General Public License for more details.
631 # You should have received a copy of the GNU General Public License
632 # along with this program. If not, see http://www.gnu.org/licenses/.
634 # - wc (GNU coreutils) 8.30
635 # Copyright (C) 2018 Free Software Foundation, Inc.
636 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
637 # This is free software: you are free to change and redistribute it.
638 # There is NO WARRANTY, to the extent permitted by law.
639 # Written by Paul Rubin and David MacKenzie.
641 # - date (GNU coreutils) 8.30
642 # Copyright (C) 2018 Free Software Foundation, Inc.
643 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
644 # This is free software: you are free to change and redistribute it.
645 # There is NO WARRANTY, to the extent permitted by law.
646 # Written by David MacKenzie.
648 # - dd (coreutils) 8.30
649 # Copyright (C) 2018 Free Software Foundation, Inc.
650 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
651 # This is free software: you are free to change and redistribute it.
652 # There is NO WARRANTY, to the extent permitted by law.
653 # Written by Paul Rubin, David MacKenzie, and Stuart Kemp.
655 # - readlink (GNU coreutils) 8.30
656 # Copyright (C) 2018 Free Software Foundation, Inc.
657 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
658 # This is free software: you are free to change and redistribute it.
659 # There is NO WARRANTY, to the extent permitted by law.
660 # Written by Dmitry V. Levin.
662 # - touch (GNU coreutils) 8.30
663 # Copyright (C) 2018 Free Software Foundation, Inc.
664 # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
665 # This is free software: you are free to change and redistribute it.
666 # There is NO WARRANTY, to the extent permitted by law.
667 # Written by Paul Rubin, Arnold Robbins, Jim Kingdon,
668 # David MacKenzie, and Randy Smith.
670 # - ots v0.7.0 ( https://github.com/opentimestamps/opentimestamps-client )
671 # The OpenTimestamps Client is free software: you can redistribute it and/or
672 # modify it under the terms of the GNU Lesser General Public License as published
673 # by the Free Software Foundation, either version 3 of the License, or (at your
674 # option) any later version.
675 # The OpenTimestamps Client is distributed in the hope that it will be useful,
676 # but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
677 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
678 # below for more details.