|
@@ -0,0 +1,349 @@
|
|
|
+unit UPCSafeBoxRootHash;
|
|
|
+
|
|
|
+{ Copyright (c) 2019 by Albert Molina
|
|
|
+
|
|
|
+ Acknowledgements:
|
|
|
+ Herman Schoenfeld <[email protected]> author of PIP-0030 (2019)
|
|
|
+
|
|
|
+ Distributed under the MIT software license, see the accompanying file LICENSE
|
|
|
+ or visit http://www.opensource.org/licenses/mit-license.php.
|
|
|
+
|
|
|
+ This unit is a part of the PascalCoin Project, an infinitely scalable
|
|
|
+ cryptocurrency. Find us here:
|
|
|
+ Web: https://www.pascalcoin.org
|
|
|
+ Source: https://github.com/PascalCoin/PascalCoin
|
|
|
+
|
|
|
+ If you like it, consider a donation using Bitcoin:
|
|
|
+ 16K3HCZRhFUtM8GdWRcfKeaa6KsuyxZaYk
|
|
|
+
|
|
|
+ THIS LICENSE HEADER MUST NOT BE REMOVED.
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+{ This unit implements the PIP-0030 proposed by Herman Schoenfeld for the Safebox
|
|
|
+ https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0030.md
|
|
|
+
|
|
|
+
|
|
|
+ Is based in:
|
|
|
+ Each "Account segment" is stored in a RAW memory buffer of type TBytesBuffer
|
|
|
+ Each "Account segment" is a 32 bytes value, that contains a SHA256 of the
|
|
|
+ information contained in a TBlockAccount:
|
|
|
+ SHA256 of (
|
|
|
+ TBlockAccount.blockchainInfo : TOperationBlock;
|
|
|
+ TBlockAccount.accounts : Array[0..4] of TAccount;
|
|
|
+ )
|
|
|
+ On current PascalCoin source code, this "Account Segment" hash is stored
|
|
|
+ in a TBytesBuffer where each 32 bytes are a iIndex of the "Account Segment" hash
|
|
|
+ Example:
|
|
|
+ - Number of "Account segments" = 1000 (that means 1000 blocks and 5000 accounts)
|
|
|
+ - TBytesBuffer size = 32 * 1000 = 32000 bytes
|
|
|
+ - index of "Account segment" position:
|
|
|
+ - Position 0 -> 0*32 = 0
|
|
|
+ - Position 123 -> 123 * 32 = 3936
|
|
|
+ - Position 1002 -> Out of range (max = 1000-1 = 999)
|
|
|
+
|
|
|
+ Calling "TPCSafeboxRootHash.CalcSafeBoxRootHash" will obtain a single 32 bytes
|
|
|
+ value as described at PIP that is the "SafeBoxRoot"
|
|
|
+
|
|
|
+ Calling "TPCSafeboxRootHash.GetProof" will return an array of 32 bytes value
|
|
|
+ that is the proof of each level that must be hashed. The 0-index is the hash
|
|
|
+ of the "Account Segment" to get Proof, and the last item of the array will be
|
|
|
+ the "SafeBoxRoot" value
|
|
|
+
|
|
|
+ Example:
|
|
|
+ Account Segments: 01 02 03 04 05 06 07 08 09 = 9 items
|
|
|
+ Level 2 process: 11 12 13 14 09 = 5 items
|
|
|
+ Level 3 process: 21 22 09 = 3 items
|
|
|
+ Level 4 process: 31 09 = 2 items
|
|
|
+ Level 5 process: 41 = 1 item = SafeBoxRoot
|
|
|
+
|
|
|
+ The "GetProof" of account segment 03 will be: 03 04 11 22 09 41
|
|
|
+ - Note that first item 03 = same account to get proof of
|
|
|
+ - Note that last item 41 = SafeBoxRoot
|
|
|
+
|
|
|
+ The "GetProof" of account segment 09 will be: 09 09 09 09 31 41
|
|
|
+ - Note that will repeat 09 value needed times one for each level (base + 3 levels)
|
|
|
+
|
|
|
+ Calling "TPCSafeboxRootHash.CheckProof" will validate a previous "GetProof"
|
|
|
+ - If the array is length = 1 then there was only 1 "Account Segment"
|
|
|
+ - The array must be: length=1 or length>2 (length=3 not allowed)
|
|
|
+ - Length 1=single account segment, so, equal to SafeBoxRoot
|
|
|
+ - 2 accounts segments: Need 3 hashes: The base, sibling and SafeBoxRoot
|
|
|
+
|
|
|
+ Speed tests:
|
|
|
+ Made on 2019-05-21 with a Intel i5-4460 CPU
|
|
|
+ - 315000 blocks (aka "Account segments") -> Aprox 3 years of PascalCoin Safebox (2016..2019)
|
|
|
+ CalcSafeBoxRootHash -> 170 ms using OpenSSL library for 32 bits
|
|
|
+ - 630000 Blocks -> Aprox 6 years of PascalCoin Safebox (2016..2022)
|
|
|
+ CalcSafeBoxRootHash -> 350 ms using OpenSSL library for 32 bits
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+interface
|
|
|
+
|
|
|
+{$I config.inc}
|
|
|
+uses
|
|
|
+ Classes, SysUtils, UConst, UCrypto, SyncObjs, UThread, UBaseTypes,
|
|
|
+ UPCOrderedLists, UPCDataTypes,
|
|
|
+ {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF};
|
|
|
+
|
|
|
+type
|
|
|
+ TProofLevels = Record
|
|
|
+ Levels : Array of TRawBytes;
|
|
|
+ End;
|
|
|
+
|
|
|
+ TPCSafeboxRootHash = Class
|
|
|
+ class function CalcNextLevelHash(ABlocksHashBuffer : TBytesBuffer; AStartIndex, ABlocksCount : Integer; ANextLevel : TBytesBuffer) : Boolean;
|
|
|
+ public
|
|
|
+ class function CalcSafeBoxRootHash(ABlocksHashBuffer : TBytesBuffer; AStartIndex, ABlocksCount : Integer) : TRawBytes; overload;
|
|
|
+ class function CalcSafeBoxRootHash(ABlocksHashBuffer : TBytesBuffer) : TRawBytes; overload;
|
|
|
+ class function GetProof(ABlocksHashBuffer : TBytesBuffer; ABlockIndex : Integer; var AProofLevels : TProofLevels) : Boolean;
|
|
|
+ class function CheckProof(ABlockIndex, ATotalBlocks : Integer; const AProofLevels : TProofLevels) : Boolean;
|
|
|
+ End;
|
|
|
+
|
|
|
+implementation
|
|
|
+
|
|
|
+{ TPCSafeboxRootHash }
|
|
|
+
|
|
|
+class function TPCSafeboxRootHash.CalcNextLevelHash(ABlocksHashBuffer: TBytesBuffer; AStartIndex, ABlocksCount: Integer; ANextLevel: TBytesBuffer): Boolean;
|
|
|
+var
|
|
|
+ i, LLeft, LRight : Integer;
|
|
|
+ LPByte : PByte;
|
|
|
+ LSHA256 : TRawBytes;
|
|
|
+ LTotalBlocks : Integer;
|
|
|
+begin
|
|
|
+ Assert((ABlocksHashBuffer.Length MOD 32)=0,'ABlocksHashBuffer invalid length ('+IntToStr(ABlocksHashBuffer.Length)+') not modulo 32 = 0');
|
|
|
+ Assert((AStartIndex>=0) And (ABlocksCount>0) And (ABlocksHashBuffer.Length>0),Format('Invalid AStartIndex:%d or ACount:%d or Length:%d',[AStartIndex,ABlocksCount,ABlocksHashBuffer.Length]));
|
|
|
+
|
|
|
+ LTotalBlocks := ABlocksHashBuffer.Length DIV 32;
|
|
|
+ ANextLevel.Clear;
|
|
|
+
|
|
|
+ if LTotalBlocks=1 then begin
|
|
|
+ ANextLevel.CopyFrom(ABlocksHashBuffer);
|
|
|
+ Exit(True);
|
|
|
+ end;
|
|
|
+
|
|
|
+ if (AStartIndex + ABlocksCount)>LTotalBlocks then Exit(False); // Invalid params
|
|
|
+
|
|
|
+ for i := 0 to ((LTotalBlocks-1) DIV 2) do begin
|
|
|
+ LLeft := i*64;
|
|
|
+ LRight := (i+1)*64;
|
|
|
+ LPByte := ABlocksHashBuffer.Memory;
|
|
|
+ Inc(LPByte,LLeft);
|
|
|
+ if (LRight>ABlocksHashBuffer.Length) then begin
|
|
|
+ ANextLevel.Add(LPByte^,32);
|
|
|
+ end else begin
|
|
|
+ LSHA256 := TCrypto.DoSha256(PAnsiChar(LPByte),64);
|
|
|
+ ANextLevel.Add(LSHA256);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ Result := True;
|
|
|
+end;
|
|
|
+
|
|
|
+class function TPCSafeboxRootHash.CalcSafeBoxRootHash(ABlocksHashBuffer: TBytesBuffer): TRawBytes;
|
|
|
+begin
|
|
|
+ Result := CalcSafeBoxRootHash(ABlocksHashBuffer,0,ABlocksHashBuffer.Length DIV 32);
|
|
|
+end;
|
|
|
+
|
|
|
+class function TPCSafeboxRootHash.CalcSafeBoxRootHash(ABlocksHashBuffer: TBytesBuffer; AStartIndex, ABlocksCount: Integer): TRawBytes;
|
|
|
+ // PRE: The ABlocksHashBuffer has a length MODULO 32 = 0 and size > 0, because contains X blocks of 32 bytes each
|
|
|
+ // each 32 bytes of ABlocksHashBuffer contains a SHA256 of TBlockAccount, extracted from TBlockAccount.block_hash
|
|
|
+
|
|
|
+ function CalculateSafeBoxRootHash(APreviousLevelBuffer : TBytesBuffer) : TRawBytes;
|
|
|
+ // PRE: APreviousLevelBuffer contains a set of 32 bytes
|
|
|
+ var LNextLevel : TBytesBuffer;
|
|
|
+ i, LLeft, LRight : Integer;
|
|
|
+ LPByte : PByte;
|
|
|
+ LSHA256 : TRawBytes;
|
|
|
+ LTotalBlocks : Integer;
|
|
|
+ begin
|
|
|
+ LTotalBlocks := APreviousLevelBuffer.Length DIV 32;
|
|
|
+
|
|
|
+ if (LTotalBlocks)=1 then begin
|
|
|
+ SetLength(Result,32);
|
|
|
+ Move(APreviousLevelBuffer.Memory^,Result[0],32);
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+
|
|
|
+ LNextLevel := TBytesBuffer.Create(APreviousLevelBuffer.Length DIV 2);
|
|
|
+ try
|
|
|
+
|
|
|
+ for i := 0 to ((LTotalBlocks-1) DIV 2) do begin
|
|
|
+ //for i := 0 to ((APreviousLevelBuffer.Length DIV 64)-1) do begin
|
|
|
+ LLeft := i*64;
|
|
|
+ LRight := (i+1)*64;
|
|
|
+ LPByte := APreviousLevelBuffer.Memory;
|
|
|
+ Inc(LPByte,LLeft);
|
|
|
+ if (LRight>APreviousLevelBuffer.Length) then begin
|
|
|
+ LNextLevel.Add(LPByte^,32);
|
|
|
+ end else begin
|
|
|
+ LSHA256 := TCrypto.DoSha256(PAnsiChar(LPByte),64);
|
|
|
+ LNextLevel.Add(LSHA256);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+ Result := CalculateSafeBoxRootHash(LNextLevel)
|
|
|
+
|
|
|
+ finally
|
|
|
+ LNextLevel.Free;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+var LHashBufferChunk : TBytesBuffer;
|
|
|
+ LTotalBlocks : Integer;
|
|
|
+ LPByte : PByte;
|
|
|
+begin
|
|
|
+ // Protection
|
|
|
+ Assert((ABlocksHashBuffer.Length MOD 32)=0,'ABlocksHashBuffer invalid length ('+IntToStr(ABlocksHashBuffer.Length)+') not modulo 32 = 0');
|
|
|
+ Assert((AStartIndex>=0) And (ABlocksCount>0) And (ABlocksHashBuffer.Length>0),Format('Invalid AStartIndex:%d or ACount:%d or Length:%d',[AStartIndex,ABlocksCount,ABlocksHashBuffer.Length]));
|
|
|
+
|
|
|
+ LTotalBlocks := ABlocksHashBuffer.Length DIV 32;
|
|
|
+
|
|
|
+ Assert((AStartIndex + ABlocksCount)<=LTotalBlocks,Format('Out of range AStartIndex:%d + ACount:%d > Blocks:%d',[AStartIndex,ABlocksCount,LTotalBlocks]));
|
|
|
+
|
|
|
+ if (AStartIndex=0) And (ABlocksCount=LTotalBlocks) then begin
|
|
|
+ Result := CalculateSafeBoxRootHash(ABlocksHashBuffer);
|
|
|
+ end else begin
|
|
|
+ LHashBufferChunk := TBytesBuffer.Create(LTotalBlocks*32);
|
|
|
+ try
|
|
|
+ LPByte := ABlocksHashBuffer.Memory;
|
|
|
+ Inc(LPByte,32 * AStartIndex);
|
|
|
+ LHashBufferChunk.Add(LPByte^, ABlocksCount*32);
|
|
|
+ //
|
|
|
+ Result := CalculateSafeBoxRootHash(LHashBufferChunk);
|
|
|
+ finally
|
|
|
+ LHashBufferChunk.Free;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+class function TPCSafeboxRootHash.CheckProof(ABlockIndex, ATotalBlocks: Integer; const AProofLevels: TProofLevels): Boolean;
|
|
|
+var iLevel : Integer;
|
|
|
+ LLevelItemsCount : Integer;
|
|
|
+ LLevelItemIndex : Integer;
|
|
|
+ LRawToHash,
|
|
|
+ LPreviousHashedValue : TRawBytes;
|
|
|
+begin
|
|
|
+ // At least 1 level (single) or >2 levels where 0=leaf and Length-1 = RootHash
|
|
|
+ if (Length(AProofLevels.Levels)=0) then Exit(False);
|
|
|
+ if (Length(AProofLevels.Levels)=2) then Exit(False);
|
|
|
+
|
|
|
+
|
|
|
+ Result := True;
|
|
|
+
|
|
|
+ if (Length(AProofLevels.Levels)=1) then Exit(True); // If only 1 level, nothing to proof, is a single proof = True
|
|
|
+
|
|
|
+ iLevel := 1;
|
|
|
+
|
|
|
+ LLevelItemsCount := ATotalBlocks;
|
|
|
+ LLevelItemIndex := ABlockIndex;
|
|
|
+ SetLength(LRawToHash,32 * 2);
|
|
|
+ LPreviousHashedValue := AProofLevels.Levels[0];
|
|
|
+ //LHashedLevel := LPreviousHashedValue;
|
|
|
+ while (iLevel<Length(AProofLevels.Levels)) do begin
|
|
|
+ // Left or right?
|
|
|
+ if (LLevelItemIndex MOD 2)=0 then begin
|
|
|
+ // Even
|
|
|
+ if (LLevelItemIndex+1<LLevelItemsCount) then begin
|
|
|
+ Move(LPreviousHashedValue[0],LRawToHash[0],32);
|
|
|
+ Move(AProofLevels.Levels[iLevel][0],LRawToHash[32],32);
|
|
|
+ LPreviousHashedValue := TCrypto.DoSha256(LRawToHash);
|
|
|
+ end
|
|
|
+ else begin
|
|
|
+ LPreviousHashedValue := Copy(LPreviousHashedValue);
|
|
|
+ end;
|
|
|
+ end else begin
|
|
|
+ // Odd, always on right side
|
|
|
+ Move(LPreviousHashedValue[0],LRawToHash[32],32);
|
|
|
+ Move(AProofLevels.Levels[iLevel][0],LRawToHash[0],32);
|
|
|
+ LPreviousHashedValue := TCrypto.DoSha256(LRawToHash);
|
|
|
+ end;
|
|
|
+ LLevelItemIndex := LLevelItemIndex DIV 2;
|
|
|
+ LLevelItemsCount := ((LLevelItemsCount-1) DIV 2)+1;
|
|
|
+ inc(iLevel);
|
|
|
+ end;
|
|
|
+ Result := TBaseType.Equals(LPreviousHashedValue,AProofLevels.Levels[High(AProofLevels.Levels)]);
|
|
|
+end;
|
|
|
+
|
|
|
+class function TPCSafeboxRootHash.GetProof(ABlocksHashBuffer: TBytesBuffer;
|
|
|
+ ABlockIndex: Integer; var AProofLevels: TProofLevels): Boolean;
|
|
|
+ // PRE: ABlockIndex is 0 indexed. Range 0..Total-1
|
|
|
+
|
|
|
+ procedure AddToProofLevels(ABlockIndexToSave : Integer; const ABlocks : TBytesBuffer);
|
|
|
+ var LPByte : PByte;
|
|
|
+ LNewProof : TRawBytes;
|
|
|
+ begin
|
|
|
+ SetLength(LNewProof,32);
|
|
|
+ LPByte := ABlocks.Memory;
|
|
|
+ Inc(LPByte,ABlockIndexToSave * 32);
|
|
|
+ Move(LPByte^,LNewProof[0],32);
|
|
|
+ //
|
|
|
+ SetLength(AProofLevels.Levels,Length(AProofLevels.Levels)+1);
|
|
|
+ AProofLevels.Levels[High(AProofLevels.Levels)] := LNewProof;
|
|
|
+ end;
|
|
|
+
|
|
|
+
|
|
|
+ procedure GetLevelProof(APreviousLevelHashBuffer: TBytesBuffer; ALevelBlockIndex : Integer; var ALevels: TProofLevels);
|
|
|
+ // PRE: At least we have 1 block
|
|
|
+
|
|
|
+ var LTotalBlocks : Integer;
|
|
|
+ LNextLevel : TBytesBuffer;
|
|
|
+ begin
|
|
|
+ LTotalBlocks := APreviousLevelHashBuffer.Length DIV 32;
|
|
|
+ // Is top level?
|
|
|
+ if LTotalBlocks=1 then begin
|
|
|
+ // Include it at top
|
|
|
+ AddToProofLevels(0, APreviousLevelHashBuffer);
|
|
|
+ Exit;
|
|
|
+ end;
|
|
|
+ // Save current level
|
|
|
+ // Even or Odd
|
|
|
+ if (ALevelBlockIndex MOD 2)=0 then begin
|
|
|
+ // Even = Left, put right one
|
|
|
+ if ALevelBlockIndex+1<LTotalBlocks then begin
|
|
|
+ AddToProofLevels(ALevelBlockIndex+1, APreviousLevelHashBuffer);
|
|
|
+ end else begin
|
|
|
+ // Last value, add itself
|
|
|
+ AddToProofLevels(ALevelBlockIndex, APreviousLevelHashBuffer);
|
|
|
+ end;
|
|
|
+ end else begin
|
|
|
+ // Odd = Right, put left one
|
|
|
+ if (ALevelBlockIndex>0) then begin
|
|
|
+ AddToProofLevels(ALevelBlockIndex-1, APreviousLevelHashBuffer);
|
|
|
+ end else begin
|
|
|
+ // First value, add itself
|
|
|
+ AddToProofLevels(0, APreviousLevelHashBuffer);
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+ // Calculate next level
|
|
|
+ LNextLevel := TBytesBuffer.Create(APreviousLevelHashBuffer.Length DIV 2);
|
|
|
+ try
|
|
|
+ CalcNextLevelHash(APreviousLevelHashBuffer,0,LTotalBlocks,LNextLevel);
|
|
|
+ GetLevelProof(LNextLevel,(ALevelBlockIndex DIV 2),ALevels);
|
|
|
+ finally
|
|
|
+ LNextLevel.Free;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+var LTotalBlocks : Integer;
|
|
|
+begin
|
|
|
+ // Protection
|
|
|
+ Assert((ABlocksHashBuffer.Length MOD 32)=0,'ABlocksHashBuffer invalid length ('+IntToStr(ABlocksHashBuffer.Length)+') not modulo 32 = 0');
|
|
|
+ // Init
|
|
|
+
|
|
|
+ SetLength(AProofLevels.Levels,0);
|
|
|
+ LTotalBlocks := ABlocksHashBuffer.Length DIV 32;
|
|
|
+ Result := False;
|
|
|
+ AProofLevels.Levels := Nil;
|
|
|
+ if LTotalBlocks<=ABlockIndex then Exit(False);
|
|
|
+ if LTotalBlocks=0 then Exit(False);
|
|
|
+ // First
|
|
|
+ Result := True;
|
|
|
+ AddToProofLevels(ABlockIndex,ABlocksHashBuffer);
|
|
|
+ if LTotalBlocks>1 then begin
|
|
|
+ GetLevelProof(ABlocksHashBuffer,ABlockIndex,AProofLevels);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+end.
|