diff options
author | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2020-07-22 21:33:22 +0100 |
---|---|---|
committer | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2020-07-24 20:33:33 +0100 |
commit | 9dbae7439d0c01c29566fc0df2cf59c5e5e1aed7 (patch) | |
tree | b239909637cdd2b40fcb040e1215dd0ee539d115 /rules/bitcoin.lua | |
parent | 51ba779bd188b0c890a763f044e3fc3c61d126ee (diff) | |
download | rspamd-9dbae7439d0c01c29566fc0df2cf59c5e5e1aed7.tar.gz rspamd-9dbae7439d0c01c29566fc0df2cf59c5e5e1aed7.zip |
[Project] Bitcoin: BTC cash addresses needs some checksum validation
Diffstat (limited to 'rules/bitcoin.lua')
-rw-r--r-- | rules/bitcoin.lua | 100 |
1 files changed, 72 insertions, 28 deletions
diff --git a/rules/bitcoin.lua b/rules/bitcoin.lua index a9e78a1c4..defba0f98 100644 --- a/rules/bitcoin.lua +++ b/rules/bitcoin.lua @@ -117,48 +117,92 @@ local function gen_bleach32_table(input) return res and d or nil end +local function slice_table(tbl, first, last, step) + local sliced = {} + + for i = first or 1, last or #tbl, step or 1 do + sliced[#sliced+1] = tbl[i] + end + + return sliced +end + +local function btc_cash_polymod_step(c, byte) + --[[ + uint8_t c0 = c >> 35; + c = ((c & 0x07ffffffff) << 5) ^ d; + + if (c0 & 0x01) c ^= 0x98f2bc8e61; + if (c0 & 0x02) c ^= 0x79b76d99e2; + if (c0 & 0x04) c ^= 0xf33e5fb3c4; + if (c0 & 0x08) c ^= 0xae2eabe2a8; + if (c0 & 0x10) c ^= 0x1e4f43e470; +--]] + local c0 = bit.rshift(c, 35) + c = bit.bxor(bit.lshift(bit.band(c, 0x07ffffffff), 5), byte) + if bit.band(c0, 0x01) ~= 0 then + c = bit.bxor(c, 0x98f2bc8e61) + end + if bit.band(c0, 0x02) ~= 0 then + c = bit.bxor(c, 0x79b76d99e2) + end + if bit.band(c0, 0x04) ~= 0 then + c = bit.bxor(c, 0xf33e5fb3c4) + end + if bit.band(c0, 0x08) ~= 0 then + c = bit.bxor(c, 0xae2eabe2a8) + end + if bit.band(c0, 0x10) ~= 0 then + c = bit.bxor(c, 0x1e4f43e470) + end + + return c +end + local function is_segwit_bech32_address(word) local semicolon_pos = string.find(word, ':') + local address_part = word if semicolon_pos then - word = string.sub(word, semicolon_pos + 1) + address_part = string.sub(word, semicolon_pos + 1) end - local prefix = word:sub(1, 3) + local prefix = address_part:sub(1, 3) if prefix == 'bc1' or prefix:sub(1, 1) == '1' or prefix:sub(1, 1) == '3' then -- Strip beach32 prefix in bitcoin - word = word:lower() - local last_one_pos = word:find('1[^1]*$') - if not last_one_pos or (last_one_pos < 1 or last_one_pos + 7 > #word) then + address_part = address_part:lower() + local last_one_pos = address_part:find('1[^1]*$') + if not last_one_pos or (last_one_pos < 1 or last_one_pos + 7 > #address_part) then return false end - local hrp = word:sub(1, last_one_pos - 1) - local addr = word:sub(last_one_pos + 1, -1) + local hrp = address_part:sub(1, last_one_pos - 1) + local addr = address_part:sub(last_one_pos + 1, -1) local decoded = gen_bleach32_table(addr) if decoded then return verify_beach32_cksum(hrp, decoded) end - else - -- BCH address - -- 1 byte address type (who cares) - -- XXX bytes address hash (who cares) - -- 40 bit checksum - local rspamd_util = require 'rspamd_util' - local decoded = rspamd_util.decode_base32(word:lower(), 'bleach') - - if decoded and #decoded > 0 then - local bytes = decoded:bytes() - - -- The version byte’s most signficant bit is reserved and must be 0. - -- The 4 next bits indicate the type of address and the 3 least significant bits indicate the size of the hash. - local version = bit.band(bytes[1], 128) - local addr_type = bit.rshift(bit.band(bytes[1], 120), 3) - local _ = bit.band(bytes[1], 7) -- hash size - - if version == 0 and (addr_type == 0 or addr_type == 8) then - -- TODO: Add checksum validation some day - + elseif semicolon_pos then + -- Bitcoin cash address + -- https://www.bitcoincash.org/spec/cashaddr.html + local l = require "rspamd_logger" + local decoded = gen_bleach32_table(address_part) + l.errx('check %s, %s decoded', word, decoded) + + if decoded and #decoded > 8 then + -- TODO: Add checksum validation some day + local c = 1 + local prefix = word:sub(1, semicolon_pos - 1) + fun.each(function(byte) + c = btc_cash_polymod_step(c, bit.band(string.byte(byte), 0x1f)) + end, fun.iter(prefix)) + + -- For semicolon + c = btc_cash_polymod_step(c, 0) + + fun.each(function(byte) c = btc_cash_polymod_step(c, byte) end, decoded) + + if bit.bxor(c, 0x1) == 0 then return true end end @@ -173,7 +217,7 @@ rspamd_config:register_symbol{ local rspamd_re = require "rspamd_regexp" local btc_wallet_re = rspamd_re.create_cached('^[13LM][1-9A-Za-z]{25,34}$') - local segwit_wallet_re = rspamd_re.create_cached('^(?:bc1|[13]|(?:[^:]*:))?[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{14,}$', 'i') + local segwit_wallet_re = rspamd_re.create_cached('^(?:bc1|[13]|(?:[^:]+:))[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{14,}$', 'i') local words_matched = {} local segwit_words_matched = {} local valid_wallets = {} |