diff --git a/src/lua/digest.lua b/src/lua/digest.lua
index 96a86e3d386e9b35223bd779aced21b522be3b91..2955caa4dc9ed6b370c61c7bbc143da6038fd2b5 100644
--- a/src/lua/digest.lua
+++ b/src/lua/digest.lua
@@ -1,7 +1,5 @@
 -- digest.lua (internal file)
 
-do
-
 local ffi = require 'ffi'
 
 ffi.cdef[[
@@ -26,13 +24,18 @@ ffi.cdef[[
     extern int32_t guava(int64_t state, int32_t buckets);
     extern crc32_func crc32_calc;
 
-   /* base64 */
-   int base64_bufsize(int binsize);
-   int base64_decode(const char *in_base64, int in_len, char *out_bin, int out_len);
-   int base64_encode(const char *in_bin, int in_len, char *out_base64, int out_len);
+    /* base64 */
+    int base64_bufsize(int binsize);
+    int base64_decode(const char *in_base64, int in_len, char *out_bin, int out_len);
+    int base64_encode(const char *in_bin, int in_len, char *out_base64, int out_len);
+
+    /* random */
+    void random_bytes(char *, size_t);
 
-   /* random */
-   void random_bytes(char *, size_t);
+    /* from third_party/PMurHash.h */
+    void PMurHash32_Process(uint32_t *ph1, uint32_t *pcarry, const void *key, int len);
+    uint32_t PMurHash32_Result(uint32_t h1, uint32_t carry, uint32_t total_length);
+    uint32_t PMurHash32(uint32_t seed, const void *key, int len);
 ]]
 
 local ssl
@@ -53,7 +56,6 @@ if ssl == nil then
     end
 end
 
-
 local def = {
     sha     = { 'SHA',    20 },
     sha224  = { 'SHA224', 28 },
@@ -74,6 +76,94 @@ local function tohex(r, size)
     return ffi.string(hexres, size * 2)
 end
 
+local PMurHash
+local PMurHash_methods = {
+
+    update = function(self, str)
+        str = tostring(str or '')
+        ffi.C.PMurHash32_Process(self.seed, self.value, str, string.len(str))
+        self.total_length = self.total_length + string.len(str)
+    end,
+
+    result = function(self)
+        return ffi.C.PMurHash32_Result(self.seed[0], self.value[0], self.total_length)
+    end,
+
+    clear = function(self)
+        self.seed[0] = self.default_seed
+        self.total_length = 0
+        self.value[0] = 0
+    end,
+
+    copy = function(self)
+        local new_self = PMurHash.new()
+        new_self.seed[0] = self.seed[0]
+        new_self.value[0] = self.value[0]
+        new_self.total_length = self.total_length
+        return new_self
+    end
+}
+
+PMurHash = {
+    default_seed = 13,
+
+    new = function(opts)
+        opts = opts or {}
+        local self = setmetatable({}, { __index = PMurHash_methods })
+        self.default_seed = (opts.seed or PMurHash.default_seed)
+        self.seed = ffi.new("int[1]", self.default_seed)
+        self.value = ffi.new("int[1]", 0)
+        self.total_length = 0
+        return self
+    end
+}
+
+setmetatable(PMurHash, {
+    __call = function(self, str)
+        str = tostring(str or '')
+        return ffi.C.PMurHash32(PMurHash.default_seed, str, string.len(str))
+    end
+})
+
+local CRC32
+local CRC32_methods = {
+    update = function(self, str)
+        str = tostring(str or '')
+        self.value = ffi.C.crc32_calc(self.value, str, string.len(str))
+    end,
+
+    result = function(self)
+        return self.value
+    end,
+
+    clear = function(self)
+        self.value = CRC32.crc_begin
+    end,
+
+    copy = function(self)
+        local new_self = CRC32.new()
+        new_self.value = self.value
+        return new_self
+    end
+}
+
+CRC32 = {
+    crc_begin = 4294967295,
+
+    new = function()
+        local self = setmetatable({}, { __index = CRC32_methods })
+        self.value = CRC32.crc_begin
+        return self
+    end
+}
+
+setmetatable(CRC32, {
+    __call = function(self, str)
+        str = tostring(str or '')
+        return ffi.C.crc32_calc(CRC32.crc_begin, str, string.len(str))
+    end
+})
+
 local m = {
     base64_encode = function(bin)
         if type(bin) ~= 'string' then
@@ -97,21 +187,10 @@ local m = {
         return ffi.string(bin, len)
     end,
 
-    crc32 = function(str)
-        if str == nil then
-            str = ''
-        else
-            str = tostring(str)
-        end
-        return ffi.C.crc32_calc(4294967295, str, string.len(str))
-    end,
+    crc32 = CRC32,
 
     crc32_update = function(crc, str)
-        if str == nil then
-            str = ''
-        else
-            str = tostring(str)
-        end
+        str = tostring(str or '')
         return ffi.C.crc32_calc(tonumber(crc), str, string.len(str))
     end,
 
@@ -146,7 +225,9 @@ local m = {
         local buf = ffi.new('char[?]', n)
         ffi.C.random_bytes(buf, n)
         return ffi.string(buf, n)
-    end
+    end,
+
+    murmur = PMurHash
 }
 
 if ssl ~= nil then
@@ -186,5 +267,3 @@ else
 end
 
 return m
-
-end
diff --git a/test/box/digest.result b/test/box/digest.result
index a087ba03bc84b072e1645cd7ea5c2c2f7676fc69..8bf68f6bdc5514fb211acb2ef24f14f01c8467e5 100644
--- a/test/box/digest.result
+++ b/test/box/digest.result
@@ -149,6 +149,32 @@ digest.crc32_update(digest.crc32('abc'), 'cde')
 ---
 - 3628146660
 ...
+crc = digest.crc32.new()
+---
+...
+crc:update('abc')
+---
+...
+crc2 = crc:copy()
+---
+...
+crc:update('cde')
+---
+...
+crc:result() == digest.crc32('abccde')
+---
+- true
+...
+crc2:update('def')
+---
+...
+crc2:result() == digest.crc32('abcdef')
+---
+- true
+...
+crc, crc2 = nil, nil
+---
+...
 digest.base64_encode('12345')
 ---
 - MTIzNDU=
@@ -193,19 +219,19 @@ digest.base64_decode(b) == s
 ...
 digest.base64_decode(nil)
 ---
-- error: 'builtin/digest.lua:91: Usage: digest.base64_decode(string)'
+- error: 'builtin/digest.lua:181: Usage: digest.base64_decode(string)'
 ...
 digest.base64_encode(nil)
 ---
-- error: 'builtin/digest.lua:80: Usage: digest.base64_encode(string)'
+- error: 'builtin/digest.lua:170: Usage: digest.base64_encode(string)'
 ...
 digest.base64_encode(123)
 ---
-- error: 'builtin/digest.lua:80: Usage: digest.base64_encode(string)'
+- error: 'builtin/digest.lua:170: Usage: digest.base64_encode(string)'
 ...
 digest.base64_decode(123)
 ---
-- error: 'builtin/digest.lua:91: Usage: digest.base64_decode(string)'
+- error: 'builtin/digest.lua:181: Usage: digest.base64_decode(string)'
 ...
 digest.guava('hello', 0)
 ---
@@ -229,7 +255,7 @@ digest.guava(1673758223894951030, 11)
 ...
 digest.urandom()
 ---
-- error: 'builtin/digest.lua:144: Usage: digest.urandom(len)'
+- error: 'builtin/digest.lua:223: Usage: digest.urandom(len)'
 ...
 #digest.urandom(0)
 ---
@@ -243,6 +269,53 @@ digest.urandom()
 ---
 - 16
 ...
+digest.murmur('1234')
+---
+- 1859914009
+...
+mur = digest.murmur.new{seed=13}
+---
+...
+nulldigest = mur:result()
+---
+...
+mur:update('1234')
+---
+...
+mur:result()
+---
+- 1859914009
+...
+mur_new = mur:copy()
+---
+...
+mur_new:update('1234')
+---
+...
+mur_new:result() ~= mur:result()
+---
+- true
+...
+mur:clear()
+---
+...
+nulldigest == mur:result()
+---
+- true
+...
+mur = digest.murmur.new{seed=14}
+---
+...
+mur:update('1234')
+---
+...
+mur:result()
+---
+- 1689834281
+...
+mur, mur_new, nulldigest = nil, nil, nil
+---
+...
 digest = nil
 ---
 ...
diff --git a/test/box/digest.test.lua b/test/box/digest.test.lua
index 5c1b8b00f22f1000802b36c3fa8eeeda11cb0959..979f911c4041f6866ae74abb98d0f88b8f9c15d3 100644
--- a/test/box/digest.test.lua
+++ b/test/box/digest.test.lua
@@ -44,6 +44,15 @@ digest.crc32_update(4294967295, 'abc')
 digest.crc32('abccde')
 digest.crc32_update(digest.crc32('abc'), 'cde')
 
+crc = digest.crc32.new()
+crc:update('abc')
+crc2 = crc:copy()
+crc:update('cde')
+crc:result() == digest.crc32('abccde')
+crc2:update('def')
+crc2:result() == digest.crc32('abcdef')
+crc, crc2 = nil, nil
+
 digest.base64_encode('12345')
 digest.base64_decode('MTIzNDU=')
 digest.base64_encode('asdfl asdf adfa zxc vzxcvz llll')
@@ -70,4 +79,19 @@ digest.urandom()
 #digest.urandom(1)
 #digest.urandom(16)
 
+digest.murmur('1234')
+mur = digest.murmur.new{seed=13}
+nulldigest = mur:result()
+mur:update('1234')
+mur:result()
+mur_new = mur:copy()
+mur_new:update('1234')
+mur_new:result() ~= mur:result()
+mur:clear()
+nulldigest == mur:result()
+mur = digest.murmur.new{seed=14}
+mur:update('1234')
+mur:result()
+mur, mur_new, nulldigest = nil, nil, nil
+
 digest = nil