Skip to content

Commit

Permalink
#5622: Produce an SHA256 hash as fingerprint.
Browse files Browse the repository at this point in the history
SHA256 implementation was taken from TDM's source code, which was in turn taken from B-Con's repository on Github.
  • Loading branch information
codereader committed May 23, 2021
1 parent 4fd43a9 commit 51512a6
Show file tree
Hide file tree
Showing 15 changed files with 343 additions and 44 deletions.
6 changes: 4 additions & 2 deletions LICENSE
@@ -1,4 +1,4 @@
DarkRadiant License (last update: 2017-09-12)
DarkRadiant License (last update: 2021-05-23)
----------------------------------------------------------------------------------------------

DarkRadiant is free software originally based on GtkRadiant, which has been licensed under
Expand All @@ -9,7 +9,7 @@ under the same GPLv2 license. Unless stated otherwise in the source file header,
applies to the DarkRadiant source code:

DarkRadiant - Open Source Level Editor for Doom 3 and The Dark Mod
Copyright (C) 2017 Matthias Baumann (on behalf of the DarkRadiant Team, see AUTHORS file)
Copyright (C) 2021 Matthias Baumann (on behalf of the DarkRadiant Team, see AUTHORS file)
==========================================================================================

This program is free software; you can redistribute it and/or modify it under the terms of
Expand All @@ -33,3 +33,5 @@ PicoModel Library - published under the modified Berkeley Software Distribution
DDS Library - published under the modified Berkeley Software Distribution (BSD) license
pybind11 Library - see the LICENSE file in the libs/pybind folder
fmtlib Library - see the LICENSE file in the libs/libfmt folder (BSD 2-clause "Simplified" License)
SHA256 implementation - by Brad Conte, released into the public domain, see this URL:
(https://github.com/B-Con/crypto-algorithms/tree/master#readme)
5 changes: 3 additions & 2 deletions include/icomparablenode.h
@@ -1,6 +1,7 @@
#pragma once

#include "inode.h"
#include <string>

namespace scene
{
Expand All @@ -15,10 +16,10 @@ class IComparableNode :
public:
virtual ~IComparableNode() {}

// Returns the fingerprint (hash) of this node, to allow for quick
// Returns the fingerprint (checksum) of this node, to allow for quick
// matching against other nodes of the same type. Fingerprints of different
// types are not comparable, be sure to check the node type first.
virtual std::size_t getFingerprint() = 0;
virtual std::string getFingerprint() = 0;
};

// The number of digits that are considered when hashing floating point values in fingerprinting
Expand Down
64 changes: 64 additions & 0 deletions libs/math/Hash.h
@@ -1,7 +1,11 @@
#pragma once

#include <cstdlib>
#include <memory>
#include <sstream>
#include <iomanip>
#include "Vector3.h"
#include "SHA256.h"

namespace math
{
Expand Down Expand Up @@ -38,4 +42,64 @@ inline std::size_t hashVector3(const Vector3& v, std::size_t significantDigits)
return xHash;
}

// Convenience wrapper around the C-style functions in the SHA256.h header
class Hash
{
private:
std::unique_ptr<SHA256_CTX> _context;

public:
Hash() :
_context(new SHA256_CTX)
{
sha256_init(_context.get());
}

void addSizet(std::size_t value)
{
sha256_update(_context.get(), reinterpret_cast<const uint8_t*>(&value), sizeof(value));
}

void addDouble(double value, std::size_t significantDigits)
{
auto intValue = static_cast<std::size_t>(value * detail::RoundingFactor(significantDigits));
addSizet(intValue);
}

void addVector3(const Vector3& v, std::size_t significantDigits)
{
std::size_t components[3] =
{
static_cast<std::size_t>(v.x() * detail::RoundingFactor(significantDigits)),
static_cast<std::size_t>(v.y() * detail::RoundingFactor(significantDigits)),
static_cast<std::size_t>(v.z() * detail::RoundingFactor(significantDigits)),
};

sha256_update(_context.get(), reinterpret_cast<const uint8_t*>(&components), sizeof(components));
}

void addString(const std::string& str)
{
if (str.length() == 0) return;

sha256_update(_context.get(), reinterpret_cast<const uint8_t*>(str.data()), str.length());
}

operator std::string() const
{
uint8_t digest[SHA256_BLOCK_SIZE];
sha256_final(_context.get(), digest);

std::ostringstream oss;
oss << std::hex << std::setfill('0');

for (uint8_t val : digest)
{
oss << std::setw(2) << (unsigned int)val;
}

return oss.str();
}
};

}
166 changes: 166 additions & 0 deletions libs/math/SHA256.cpp
@@ -0,0 +1,166 @@
/*********************************************************************
* Filename: sha256.c
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Implementation of the SHA-256 hashing algorithm.
SHA-256 is one of the three algorithms in the SHA2
specification. The others, SHA-384 and SHA-512, are not
offered in this implementation.
Algorithm specification can be found here:
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
This implementation uses little endian byte order.
*********************************************************************/

/*************************** HEADER FILES ***************************/
#include <stdlib.h>
#include <memory.h>
#include "sha256.h"

namespace math
{

typedef uint8_t BYTE;
typedef uint32_t WORD;

/****************************** MACROS ******************************/
#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))

#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))

/**************************** VARIABLES *****************************/
static const WORD k[64] = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};

/*********************** FUNCTION DEFINITIONS ***********************/
void sha256_transform(SHA256_CTX* ctx, const BYTE data[])
{
WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];

for (i = 0, j = 0; i < 16; ++i, j += 4)
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
for (; i < 64; ++i)
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];

a = ctx->state[0];
b = ctx->state[1];
c = ctx->state[2];
d = ctx->state[3];
e = ctx->state[4];
f = ctx->state[5];
g = ctx->state[6];
h = ctx->state[7];

for (i = 0; i < 64; ++i) {
t1 = h + EP1(e) + CH(e, f, g) + k[i] + m[i];
t2 = EP0(a) + MAJ(a, b, c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}

ctx->state[0] += a;
ctx->state[1] += b;
ctx->state[2] += c;
ctx->state[3] += d;
ctx->state[4] += e;
ctx->state[5] += f;
ctx->state[6] += g;
ctx->state[7] += h;
}

void sha256_init(SHA256_CTX* ctx)
{
ctx->datalen = 0;
ctx->bitlen = 0;
ctx->state[0] = 0x6a09e667;
ctx->state[1] = 0xbb67ae85;
ctx->state[2] = 0x3c6ef372;
ctx->state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f;
ctx->state[5] = 0x9b05688c;
ctx->state[6] = 0x1f83d9ab;
ctx->state[7] = 0x5be0cd19;
}

void sha256_update(SHA256_CTX* ctx, const BYTE data[], size_t len)
{
WORD i;

for (i = 0; i < len; ++i) {
ctx->data[ctx->datalen] = data[i];
ctx->datalen++;
if (ctx->datalen == 64) {
sha256_transform(ctx, ctx->data);
ctx->bitlen += 512;
ctx->datalen = 0;
}
}
}

void sha256_final(SHA256_CTX* ctx, BYTE hash[])
{
WORD i;

i = ctx->datalen;

// Pad whatever data is left in the buffer.
if (ctx->datalen < 56) {
ctx->data[i++] = 0x80;
while (i < 56)
ctx->data[i++] = 0x00;
}
else {
ctx->data[i++] = 0x80;
while (i < 64)
ctx->data[i++] = 0x00;
sha256_transform(ctx, ctx->data);
memset(ctx->data, 0, 56);
}

// Append to the padding the total message's length in bits and transform.
ctx->bitlen += ctx->datalen * 8;
ctx->data[63] = (uint8_t)(ctx->bitlen);
ctx->data[62] = (uint8_t)(ctx->bitlen >> 8);
ctx->data[61] = (uint8_t)(ctx->bitlen >> 16);
ctx->data[60] = (uint8_t)(ctx->bitlen >> 24);
ctx->data[59] = (uint8_t)(ctx->bitlen >> 32);
ctx->data[58] = (uint8_t)(ctx->bitlen >> 40);
ctx->data[57] = (uint8_t)(ctx->bitlen >> 48);
ctx->data[56] = (uint8_t)(ctx->bitlen >> 56);
sha256_transform(ctx, ctx->data);

// Since this implementation uses little endian byte ordering and SHA uses big endian,
// reverse all the bytes when copying the final state to the output hash.
for (i = 0; i < 4; ++i) {
hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
}
}

}
39 changes: 39 additions & 0 deletions libs/math/SHA256.h
@@ -0,0 +1,39 @@
#pragma once

/*********************************************************************
* Filename: sha256.h
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Defines the API for the corresponding SHA1 implementation.
*
* Source: https://github.com/B-Con/crypto-algorithms/blob/cfbde48414baacf51fc7c74f275190881f037d32/sha256.c
* Modified by stgatilov: use stdint types, allow inclusion from C++;
* greebo: Moved to namespace math
*********************************************************************/

/*************************** HEADER FILES ***************************/
#include <stddef.h>
#include <stdint.h>

namespace math
{

/****************************** MACROS ******************************/
#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest

/**************************** DATA TYPES ****************************/

typedef struct SHA256_CTX {
uint8_t data[64];
uint32_t datalen;
uint64_t bitlen;
uint32_t state[8];
} SHA256_CTX;

/*********************** FUNCTION DECLARATIONS **********************/
void sha256_init(SHA256_CTX *ctx);
void sha256_update(SHA256_CTX *ctx, const uint8_t data[], size_t len);
void sha256_final(SHA256_CTX *ctx, uint8_t hash[]);

}
28 changes: 15 additions & 13 deletions radiantcore/brush/BrushNode.cpp
Expand Up @@ -61,37 +61,39 @@ const AABB& BrushNode::localAABB() const {
return m_brush.localAABB();
}

std::size_t BrushNode::getFingerprint()
std::string BrushNode::getFingerprint()
{
constexpr std::size_t SignificantDigits = scene::SignificantFingerprintDoubleDigits;

if (m_brush.getNumFaces() == 0)
{
return 0; // empty brushes produce a zero fingerprint
return std::string(); // empty brushes produce an empty fingerprint
}

auto hash = static_cast<std::size_t>(m_brush.getDetailFlag() + 1);
math::Hash hash;

hash.addSizet(static_cast<std::size_t>(m_brush.getDetailFlag() + 1));

math::combineHash(hash, m_brush.getNumFaces());
hash.addSizet(m_brush.getNumFaces());

// Combine all face plane equations
for (const auto& face : m_brush)
{
// Plane equation
math::combineHash(hash, math::hashVector3(face->getPlane3().normal(), SignificantDigits));
math::combineHash(hash, math::hashDouble(face->getPlane3().dist(), SignificantDigits));
hash.addVector3(face->getPlane3().normal(), SignificantDigits);
hash.addDouble(face->getPlane3().dist(), SignificantDigits);

// Material Name
math::combineHash(hash, std::hash<std::string>()(face->getShader()));
hash.addString(face->getShader());

// Texture Matrix
auto texdef = face->getTexDefMatrix();
math::combineHash(hash, math::hashDouble(texdef.xx(), SignificantDigits));
math::combineHash(hash, math::hashDouble(texdef.yx(), SignificantDigits));
math::combineHash(hash, math::hashDouble(texdef.tx(), SignificantDigits));
math::combineHash(hash, math::hashDouble(texdef.xy(), SignificantDigits));
math::combineHash(hash, math::hashDouble(texdef.yy(), SignificantDigits));
math::combineHash(hash, math::hashDouble(texdef.ty(), SignificantDigits));
hash.addDouble(texdef.xx(), SignificantDigits);
hash.addDouble(texdef.yx(), SignificantDigits);
hash.addDouble(texdef.tx(), SignificantDigits);
hash.addDouble(texdef.xy(), SignificantDigits);
hash.addDouble(texdef.yy(), SignificantDigits);
hash.addDouble(texdef.ty(), SignificantDigits);
}

return hash;
Expand Down
2 changes: 1 addition & 1 deletion radiantcore/brush/BrushNode.h
Expand Up @@ -85,7 +85,7 @@ class BrushNode :
Type getNodeType() const override;

// IComparable implementation
std::size_t getFingerprint() override;
std::string getFingerprint() override;

// Bounded implementation
virtual const AABB& localAABB() const override;
Expand Down

0 comments on commit 51512a6

Please sign in to comment.