import struct
 import sys
 import time
+import types
 try:
     import enum34
 except ImportError as exn:
                    , "NaCl_LEN" : 16 })
         , "enc": "aes-gcm" } }
 
-
 ###############################################################################
 ## constants
 ###############################################################################
     return SCRYPT_KEY_MEMO [key_parms], nacl
 
 
-def kdf_by_version (paramversion):
-    defs = ENCRYPTION_PARAMETERS.get(paramversion)
+def kdf_by_version (paramversion=None, defs=None):
+    if paramversion is not None:
+        defs = ENCRYPTION_PARAMETERS.get(paramversion, None)
     if defs is None:
         raise ValueError ("no encryption parameters for version %r"
                 % paramversion)
 ## freestanding invocation
 ###############################################################################
 
+PDTCRYPT_SUB_PROCESS = 0
+PDTCRYPT_SUB_SCRYPT  = 1
+
+PDTCRYPT_SUB = \
+        { "process" : PDTCRYPT_SUB_PROCESS
+        , "scrypt"  : PDTCRYPT_SUB_SCRYPT }
+
 PDTCRYPT_DECRYPT   = 1 << 0 # decrypt archive with password
 PDTCRYPT_SPLIT     = 1 << 1 # split archive into individual objects
+PDTCRYPT_HASH      = 1 << 2 # output scrypt hash for file and given password
 
 PDTCRYPT_SPLITNAME = "pdtcrypt-object-%d.bin"
 
             return depdtcrypt (pw, ins, outs)
 
 
+def mode_depdtcrypt (mode, pw, ins, outs):
+    try:
+        total_read, total_obj, total_ct, total_pt = \
+            depdtcrypt (mode, pw, ins, outs)
+    except DecryptionError as exn:
+        noise ("PDT: Decryption failed:")
+        noise ("PDT:")
+        noise ("PDT:    “%s”" % exn)
+        noise ("PDT:")
+        noise ("PDT: Did you specify the correct password?")
+        noise ("")
+        return 1
+    except PDTSplitError as exn:
+        noise ("PDT: Split operation failed:")
+        noise ("PDT:")
+        noise ("PDT:    “%s”" % exn)
+        noise ("PDT:")
+        noise ("PDT: Hint: target directory should to be empty.")
+        noise ("")
+        return 1
+
+    if PDTCRYPT_VERBOSE is True:
+        noise ("PDT: decryption successful"                 )
+        noise ("PDT:   %.10d bytes read"        % total_read)
+        noise ("PDT:   %.10d objects decrypted" % total_obj )
+        noise ("PDT:   %.10d bytes ciphertext"  % total_ct  )
+        noise ("PDT:   %.10d bytes plaintext"   % total_pt  )
+        noise (""                                           )
+
+    return 0
+
+
+def mode_scrypt (pw, ins):
+    hdr = None
+    try:
+        hdr = hdr_read_stream (ins)
+    except EndOfFile as exn:
+        noise ("PDT: malformed input: end of file reading first object header")
+        noise ("PDT:")
+        return 1
+    finally:
+        ins.close ()
+
+    nacl = hdr ["nacl"]
+    pver = hdr ["paramversion"]
+    if PDTCRYPT_VERBOSE is True:
+        noise ("PDT: salt of first object          : %s" % binascii.hexlify (nacl))
+        noise ("PDT: parameter version of archive  : %d" % pver)
+
+    try:
+        defs = ENCRYPTION_PARAMETERS.get(pver, None)
+        kdfname, params = defs ["kdf"]
+        if kdfname != "scrypt":
+            noise ("PDT: input is not an SCRYPT archive")
+            noise ("")
+            return 1
+        kdf = kdf_by_version (None, defs)
+    except ValueError as exn:
+        noise ("PDT: object has unknown parameter version %d" % pver)
+
+    hsh, _void = kdf (pw, nacl)
+
+    import json
+    out = json.dumps ({ "salt"          : str (binascii.hexlify (nacl))
+                      , "hash"          : str (binascii.hexlify (hsh))
+                      , "scrypt_params" : { "N"     : params ["N"]
+                                          , "r"     : params ["r"]
+                                          , "p"     : params ["p"]
+                                          , "dkLen" : params ["dkLen"] } })
+    print (out)
+
+
 def usage (err=False):
     out = print
     if err is True:
         out = noise
-    out ("usage: %s { --help" % SELF)
-    out ("          | [ -v ] { PASSWORD } { { -i | --in }  { - | SOURCE } }")
-    out ("                                { { -o | --out } { - | DESTINATION } }")
-    out ("                                { -D | --no-decrypt } { -S | --split }")
+    out ("usage: %s SUBCOMMAND { --help" % SELF)
+    out ("                     | [ -v ] { PASSWORD } { { -i | --in }  { - | SOURCE } }")
+    out ("                                           { { -o | --out } { - | DESTINATION } }")
+    out ("                                           { -D | --no-decrypt } { -S | --split }")
     out ("")
     out ("\twhere")
+    out ("\t\tSUBCOMMAND      main mode: { process | scrypt }")
+    out ("\t\t                where:")
+    out ("\t\t                   process: extract objects from PDT archive")
+    out ("\t\t                   scrypt:  calculate hash from password and first object")
     out ("\t\tPASSWORD        password to derive the encryption key from")
     out ("\t\t-s              enforce strict handling of initialization vectors")
     out ("\t\t-i SOURCE       file name to read from")
     argvi = iter (argv)
     SELF  = os.path.basename (next (argvi))
 
+    try:
+        rawsubcmd  = next (argvi)
+        subcommand = PDTCRYPT_SUB [rawsubcmd]
+    except StopIteration:
+        noise ("ERROR: subcommand required")
+        noise ("")
+        usage (err=True)
+        raise Unreachable
+    except KeyError:
+        noise ("ERROR: invalid subcommand “%s” specified" % rawsubcmd)
+        noise ("")
+        usage (err=True)
+        raise Unreachable
+
+    def checked_pw (arg):
+        nonlocal pw
+        if pw is None:
+            pw = arg
+        else:
+            noise ("ERROR: unqualified argument “%s” but password already "
+                "given" % arg)
+            noise ("")
+            usage (err=True)
+            raise Unreachable
+
     for arg in argvi:
         if arg in [ "-h", "--help" ]:
             usage ()
         elif arg in [ "-v", "--verbose", "--wtf" ]:
             global PDTCRYPT_VERBOSE
             PDTCRYPT_VERBOSE = True
-        elif arg in [ "-s", "--strict-ivs" ]:
-            global PDTCRYPT_STRICTIVS
-            PDTCRYPT_STRICTIVS = True
         elif arg in [ "-i", "--in", "--source" ]:
             insspec = next (argvi)
             if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt from %s" % insspec)
         elif arg in [ "-o", "--out", "--dest", "--sink" ]:
             outsspec = next (argvi)
             if PDTCRYPT_VERBOSE is True: noise ("PDT: decrypt to %s" % outsspec)
-        elif arg in [ "-f", "--force" ]:
-            global PDTCRYPT_OVERWRITE
-            PDTCRYPT_OVERWRITE = True
-            if PDTCRYPT_VERBOSE is True: noise ("PDT: overwrite existing files")
-        elif arg in [ "-S", "--split" ]:
-            mode |= PDTCRYPT_SPLIT
-            if PDTCRYPT_VERBOSE is True: noise ("PDT: split files")
-        elif arg in [ "-D", "--no-decrypt" ]:
-            mode &= ~PDTCRYPT_DECRYPT
-            if PDTCRYPT_VERBOSE is True: noise ("PDT: not decrypting")
         else:
-            if pw is None:
-                pw = arg
+            if subcommand == PDTCRYPT_SUB_PROCESS:
+                if arg in [ "-s", "--strict-ivs" ]:
+                    global PDTCRYPT_STRICTIVS
+                    PDTCRYPT_STRICTIVS = True
+                elif arg in [ "-f", "--force" ]:
+                    global PDTCRYPT_OVERWRITE
+                    PDTCRYPT_OVERWRITE = True
+                    if PDTCRYPT_VERBOSE is True: noise ("PDT: overwrite existing files")
+                elif arg in [ "-S", "--split" ]:
+                    mode |= PDTCRYPT_SPLIT
+                    if PDTCRYPT_VERBOSE is True: noise ("PDT: split files")
+                elif arg in [ "-D", "--no-decrypt" ]:
+                    mode &= ~PDTCRYPT_DECRYPT
+                    if PDTCRYPT_VERBOSE is True: noise ("PDT: not decrypting")
+                else:
+                    checked_pw (arg)
+            elif subcommand == PDTCRYPT_SUB_SCRYPT:
+                checked_pw (arg)
             else:
-                noise ("ERROR: unqualified argument “%s” but password already "
-                       "given" % arg)
-                noise ("")
-                usage (err=True)
                 raise Unreachable
 
-    if mode & PDTCRYPT_DECRYPT and pw is None:
-        noise ("ERROR: encryption requested but no password given")
-        noise ("")
-        usage (err=True)
-        raise Unreachable
+    if pw is not None:
+        pw = pw.encode ()
+    else:
+        if subcommand == PDTCRYPT_SUB_SCRYPT:
+            noise ("ERROR: scrypt hash mode requested but no password given")
+            noise ("")
+            usage (err=True)
+            raise Unreachable
+        elif mode & PDTCRYPT_DECRYPT:
+            noise ("ERROR: encryption requested but no password given")
+            noise ("")
+            usage (err=True)
+            raise Unreachable
 
     # default to stdout
     ins  = deptdcrypt_mk_stream (PDTCRYPT_SOURCE, insspec  or "-")
+
+    if subcommand == PDTCRYPT_SUB_SCRYPT:
+        return True, partial (mode_scrypt, pw, ins)
+
     if mode & PDTCRYPT_SPLIT: # destination must be directory
         if outsspec is None or outsspec == "-":
             noise ("ERROR: split mode is incompatible with stdout sink")
             noise ("")
             usage (err=True)
             raise Unreachable
+
     else:
         outs = deptdcrypt_mk_stream (PDTCRYPT_SINK, outsspec or "-")
-    return mode, pw, ins, outs
+
+    return True, partial (mode_depdtcrypt, mode, pw, ins, outs)
 
 
 def main (argv):
-    mode, pw, ins, outs  = parse_argv (argv)
-    try:
-        total_read, total_obj, total_ct, total_pt = \
-            depdtcrypt (mode, pw, ins, outs)
-    except DecryptionError as exn:
-        noise ("PDT: Decryption failed:")
-        noise ("PDT:")
-        noise ("PDT:    “%s”" % exn)
-        noise ("PDT:")
-        noise ("PDT: Did you specify the correct password?")
-        noise ("")
-        return 1
-    except PDTSplitError as exn:
-        noise ("PDT: Split operation failed:")
-        noise ("PDT:")
-        noise ("PDT:    “%s”" % exn)
-        noise ("PDT:")
-        noise ("PDT: Hint: target directory should to be empty.")
-        noise ("")
-        return 1
+    ok, runner = parse_argv (argv)
 
-    if PDTCRYPT_VERBOSE is True:
-        noise ("PDT: decryption successful"                 )
-        noise ("PDT:   %.10d bytes read"        % total_read)
-        noise ("PDT:   %.10d objects decrypted" % total_obj )
-        noise ("PDT:   %.10d bytes ciphertext"  % total_ct  )
-        noise ("PDT:   %.10d bytes plaintext"   % total_pt  )
-        noise (""                                           )
+    if ok is True: return runner ()
 
-    return 0
+    return 1
 
 
 if __name__ == "__main__":