Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/crypto/sha256.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,60 @@ void CSHA256::Midstate(unsigned char hash[OUTPUT_SIZE], uint64_t* len, unsigned
}
}

std::vector<unsigned char> CSHA256::Save() const {
size_t buf_size = bytes % 64;
std::vector<unsigned char> result(40 + buf_size);

WriteBE32(&result[ 0], s[0]);
WriteBE32(&result[ 4], s[1]);
WriteBE32(&result[ 8], s[2]);
WriteBE32(&result[12], s[3]);
WriteBE32(&result[16], s[4]);
WriteBE32(&result[20], s[5]);
WriteBE32(&result[24], s[6]);
WriteBE32(&result[28], s[7]);

WriteLE64(&result[32], bytes << 3);

memcpy(&result[40], buf, buf_size);

return result;
}

bool CSHA256::Load(const std::vector<unsigned char>& vch) {
if (vch.size() < 40) return false;

uint64_t bits = ReadLE64(&vch[32]);
size_t buf_size = (bits >> 3) % 64;

if ((bits & 0x07) != 0 || vch.size() != 40 + buf_size) return false;
Copy link
Contributor

@dgpv dgpv Feb 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment to state that bits & 0x07 check is to prevent malleability ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit more than that: the SHA256 class is simply incapable of processing sub-byte data as it stands. But I can write a comment to that effect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean you could just just do bits &= ~0x07 and not do the check, but this would introduce non-determinism, and the comment could tell the reader why it is not a good idea

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had hoped it goes without saying that one shouldn't mangle an invalidly serialized data blob into a valid one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validity of serialized data depends on the definition of the validity, but it is not explicitly specified (the serialization code in Save() is only one possible implementation of implicit specification). But, on the other hand, you can view the (bits & 0x07) != 0 as the statement of the de-facto specification "low-order bits should be zero", and in this light, no extra comment seems necessary.


// We want to leave the internal state of the object unchanged if false is returned.
// So no member variables can be modified until now.

s[0] = ReadBE32(&vch[ 0]);
s[1] = ReadBE32(&vch[ 4]);
s[2] = ReadBE32(&vch[ 8]);
s[3] = ReadBE32(&vch[12]);
s[4] = ReadBE32(&vch[16]);
s[5] = ReadBE32(&vch[20]);
s[6] = ReadBE32(&vch[24]);
s[7] = ReadBE32(&vch[28]);

bytes = bits >> 3;

memcpy(buf, &vch[40], buf_size);

return true;
}

bool CSHA256::SafeWrite(const unsigned char* data, size_t len) {
const uint64_t SHA256_MAX = 0x1FFFFFFFFFFFFFFF; // SHA256's maximum allowed message length in bytes.
if (SHA256_MAX < bytes || SHA256_MAX - bytes < len) return false;
Copy link
Contributor

@dgpv dgpv Feb 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking for SHA256_MAX - MAX_SCRIPT_ELEMENT_SIZE in Load() would allow to get rid of three ifs (if !ctx.SafeWrite(...) -> ctx.Write(...)), because Write() will be always safe in regard to this check: the size of data on the stack cannot be bigger than MAX_SCRIPT_ELEMENT_SIZE. Why you think that doing this in Write() is better ? Note that in OP_SHA256_INITIALIZE it is even impossible to fail this check, because the CTX is new and can only increase up to MAX_SCRIPT_ELEMENT_SIZE. I think an if check is due in Load() and here this condition can just be checked with assert to be safe.

Copy link
Contributor

@dgpv dgpv Feb 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it because you want to allow for UPDATE with user-supplied midstate to be able to go up to SHA256_MAX exactly ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cold make Load() take the length and then check SHA_MAX - bytes < len inside Load().
Although this makes Load/Save api less generic. But if you want it to be more generic so it can be used in other places, you probably want that check inside Load() anyway, because in general you cannot prevent anyone to use just Write() instead of SafeWrite() after Load()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose I could write Load to return a int64_t that returns the number of bytes used so far (or alternatively the number of remaining bytes available to be processed), and return -1 for failure.

Is this really all worthwhile just to avoid one if statement in SHA256INITALIZE?

Copy link
Contributor Author

@roconnor-blockstream roconnor-blockstream Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we also get rid of SafeWrite which is kinda nice to get rid of.

Copy link
Contributor

@dgpv dgpv Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all three if statements that are in place for SafeWrite now are not needed if Write is always safe, if the check SHA_MAX - bytes < len is done inside Load() - at least in regard to manipulating the state from the script. The Load() can just fail if the subsequent Write() will be invalid. But my opinion is not strong on this - this is mostly bikeshedding. But I would like to add that check inside Load() would follow 'fail early' principle, which I favour.

Copy link
Contributor Author

@roconnor-blockstream roconnor-blockstream Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having Load take in an argument of the number of bytes I intend to write later is such an unusual API, that I am very hesitant to implement it that way.

Copy link
Contributor

@dgpv dgpv Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just rename Load to Prepare then :-)

Copy link
Contributor

@dgpv dgpv Mar 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said, my opinion on this is not strong. Speaking of returning the number of bytes still available to process from Load and then checking it might be more messy than SafeWrite approach, because you will need to have that check in two places (in UPDATE and FINALIZE)

Write(data, len);
return true;
}

CSHA256& CSHA256::Reset()
{
bytes = 0;
Expand Down
4 changes: 4 additions & 0 deletions src/crypto/sha256.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <string>
#include <vector>

/** A hasher class for SHA-256. */
class CSHA256
Expand All @@ -26,7 +27,10 @@ class CSHA256
//TODO: Midstate is a hack'ish speedup that probably should make way for something
//akin to the SHA256D64 speedups
void Midstate(unsigned char hash[OUTPUT_SIZE], uint64_t* len, unsigned char *buffer);
std::vector<unsigned char> Save() const;
bool Load(const std::vector<unsigned char>& vch);
CSHA256& Reset();
bool SafeWrite(const unsigned char* data, size_t len);
};

/** Autodetect the best available SHA256 implementation.
Expand Down
59 changes: 59 additions & 0 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,65 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
}
break;

case OP_SHA256INITIALIZE: // (in -- sha256_ctx)
{
if (stack.size() < 1)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

CSHA256 ctx;
valtype& vch = stacktop(-1);
if (!ctx.SafeWrite(vch.data(), vch.size()))
return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);

popstack(stack);
stack.push_back(ctx.Save());
}
break;

case OP_SHA256UPDATE: // (sha256_ctx in -- sha256_ctx)
{
if (stack.size() < 2)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

CSHA256 ctx;
valtype& vchCtx = stacktop(-2);
if (!ctx.Load(vchCtx))
return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);

valtype& vch = stacktop(-1);
if (!ctx.SafeWrite(vch.data(), vch.size()))
return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);

popstack(stack);
popstack(stack);
stack.push_back(ctx.Save());
}
break;

case OP_SHA256FINALIZE:
{
// (sha256_ctx in -- hash)
if (stack.size() < 2)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

valtype& vchCtx = stacktop(-2);
CSHA256 ctx;
if (!ctx.Load(vchCtx))
return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);

valtype& vch = stacktop(-1);
if (!ctx.SafeWrite(vch.data(), vch.size()))
return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);

valtype vchHash(32);
ctx.Finalize(vchHash.data());

popstack(stack);
popstack(stack);
stack.push_back(vchHash);
}
break;

default:
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
}
Expand Down
3 changes: 3 additions & 0 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ const char* GetOpName(opcodetype opcode)
case OP_DETERMINISTICRANDOM : return "OP_DETERMINISTICRANDOM";
case OP_CHECKSIGFROMSTACK : return "OP_CHECKSIGFROMSTACK";
case OP_CHECKSIGFROMSTACKVERIFY: return "OP_CHECKSIGFROMSTACKVERIFY";
case OP_SHA256INITIALIZE : return "OP_SHA256INITIALIZE";
case OP_SHA256UPDATE : return "OP_SHA256UPDATE";
case OP_SHA256FINALIZE : return "OP_SHA256FINALIZE";

// expansion
case OP_NOP1 : return "OP_NOP1";
Expand Down
5 changes: 4 additions & 1 deletion src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ enum opcodetype
OP_DETERMINISTICRANDOM = 0xc0,
OP_CHECKSIGFROMSTACK = 0xc1,
OP_CHECKSIGFROMSTACKVERIFY = 0xc2,
OP_SHA256INITIALIZE = 0xc4,
OP_SHA256UPDATE = 0xc5,
OP_SHA256FINALIZE = 0xc6,

// expansion
OP_NOP1 = 0xb0,
Expand All @@ -203,7 +206,7 @@ enum opcodetype
};

// Maximum value that an opcode can be
static const unsigned int MAX_OPCODE = OP_SUBSTR_LAZY; // 0xc3
static const unsigned int MAX_OPCODE = OP_SHA256FINALIZE; // 0xc6

const char* GetOpName(opcodetype opcode);

Expand Down