UPCSafeBoxRootHash.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. unit UPCSafeBoxRootHash;
  2. { Copyright (c) 2019 by Albert Molina
  3. Acknowledgements:
  4. Herman Schoenfeld <[email protected]> author of PIP-0030 (2019)
  5. Distributed under the MIT software license, see the accompanying file LICENSE
  6. or visit http://www.opensource.org/licenses/mit-license.php.
  7. This unit is a part of the PascalCoin Project, an infinitely scalable
  8. cryptocurrency. Find us here:
  9. Web: https://www.pascalcoin.org
  10. Source: https://github.com/PascalCoin/PascalCoin
  11. If you like it, consider a donation using Bitcoin:
  12. 16K3HCZRhFUtM8GdWRcfKeaa6KsuyxZaYk
  13. THIS LICENSE HEADER MUST NOT BE REMOVED.
  14. }
  15. { This unit implements the PIP-0030 proposed by Herman Schoenfeld for the Safebox
  16. https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0030.md
  17. Is based in:
  18. Each "Account segment" is stored in a RAW memory buffer of type TBytesBuffer
  19. Each "Account segment" is a 32 bytes value, that contains a SHA256 of the
  20. information contained in a TBlockAccount:
  21. SHA256 of (
  22. TBlockAccount.blockchainInfo : TOperationBlock;
  23. TBlockAccount.accounts : Array[0..4] of TAccount;
  24. )
  25. On current PascalCoin source code, this "Account Segment" hash is stored
  26. in a TBytesBuffer where each 32 bytes are a iIndex of the "Account Segment" hash
  27. Example:
  28. - Number of "Account segments" = 1000 (that means 1000 blocks and 5000 accounts)
  29. - TBytesBuffer size = 32 * 1000 = 32000 bytes
  30. - index of "Account segment" position:
  31. - Position 0 -> 0*32 = 0
  32. - Position 123 -> 123 * 32 = 3936
  33. - Position 1002 -> Out of range (max = 1000-1 = 999)
  34. Calling "TPCSafeboxRootHash.CalcSafeBoxRootHash" will obtain a single 32 bytes
  35. value as described at PIP that is the "SafeBoxRoot"
  36. Calling "TPCSafeboxRootHash.GetProof" will return an array of 32 bytes value
  37. that is the proof of each level that must be hashed. The 0-index is the hash
  38. of the "Account Segment" to get Proof, and the last item of the array will be
  39. the "SafeBoxRoot" value
  40. Example:
  41. Account Segments: 01 02 03 04 05 06 07 08 09 = 9 items
  42. Level 2 process: 11 12 13 14 09 = 5 items
  43. Level 3 process: 21 22 09 = 3 items
  44. Level 4 process: 31 09 = 2 items
  45. Level 5 process: 41 = 1 item = SafeBoxRoot
  46. The "GetProof" of account segment 03 will be: 03 04 11 22 09 41
  47. - Note that first item 03 = same account to get proof of
  48. - Note that last item 41 = SafeBoxRoot
  49. The "GetProof" of account segment 09 will be: 09 09 09 09 31 41
  50. - Note that will repeat 09 value needed times one for each level (base + 3 levels)
  51. Calling "TPCSafeboxRootHash.CheckProof" will validate a previous "GetProof"
  52. - If the array is length = 1 then there was only 1 "Account Segment"
  53. - The array must be: length=1 or length>2 (length=2 not allowed)
  54. - Length 1=single account segment, so, equal to SafeBoxRoot
  55. - 2 accounts segments: Need 3 hashes: The base, sibling and SafeBoxRoot
  56. Speed tests:
  57. Made on 2019-05-21 with a Intel i5-4460 CPU
  58. - 315000 blocks (aka "Account segments") -> Aprox 3 years of PascalCoin Safebox (2016..2019)
  59. CalcSafeBoxRootHash -> 170 ms using OpenSSL library for 32 bits
  60. - 630000 Blocks -> Aprox 6 years of PascalCoin Safebox (2016..2022)
  61. CalcSafeBoxRootHash -> 350 ms using OpenSSL library for 32 bits
  62. }
  63. interface
  64. {$I config.inc}
  65. uses
  66. Classes, SysUtils, UConst, UCrypto, SyncObjs, UThread, UBaseTypes,
  67. UPCOrderedLists, UPCDataTypes,
  68. {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF};
  69. type
  70. TProofLevels = Record
  71. Levels : Array of TRawBytes;
  72. End;
  73. TSafeboxHashCalcType = (sbh_Single_Sha256, sbh_Merkle_Root_Hash);
  74. { TBytesBuffer32Safebox is an extension of a TBytesBuffer that will
  75. automatically update and calc the SafeboxRootHash when
  76. SafeBoxHashCalcType = sbh_Merkle_Root_Hash
  77. This will increace speed because will only calculate modified
  78. blocks when used properly, mantaining integrity of the SafeBoxHash value
  79. When SafeBoxHashCalcType = sbh_Single_Sha256 (Default) then there is
  80. no change versus superclass type TBytesBuffer}
  81. TBytesBuffer32Safebox = Class(TBytesBuffer)
  82. private
  83. FNextLevelBytesBuffer : TBytesBuffer32Safebox;
  84. FSafeBoxHashCalcType: TSafeboxHashCalcType;
  85. procedure SetSafeBoxHashCalcType(const Value: TSafeboxHashCalcType);
  86. protected
  87. procedure NotifyUpdated(AStartPos, ACountBytes : Integer); override;
  88. procedure RedoNextLevelsForMerkleRootHash;
  89. public
  90. constructor Create(ADefaultIncrement : Integer); override;
  91. destructor Destroy; override;
  92. function GetSafeBoxHash : TRawBytes;
  93. property SafeBoxHashCalcType : TSafeboxHashCalcType read FSafeBoxHashCalcType write SetSafeBoxHashCalcType;
  94. End;
  95. TPCSafeboxRootHash = Class
  96. class function CalcNextLevelHash(ABlocksHashBuffer : TBytesBuffer; AStartIndex, ABlocksCount : Integer; ANextLevel : TBytesBuffer) : Boolean;
  97. public
  98. class function CalcSafeBoxRootHash(ABlocksHashBuffer : TBytesBuffer; AStartIndex, ABlocksCount : Integer) : TRawBytes; overload;
  99. class function CalcSafeBoxRootHash(ABlocksHashBuffer : TBytesBuffer) : TRawBytes; overload;
  100. class function GetProof(ABlocksHashBuffer : TBytesBuffer; ABlockIndex : Integer; var AProofLevels : TProofLevels) : Boolean;
  101. class function CheckProof(ABlockIndex, ATotalBlocks : Integer; const AProofLevels : TProofLevels) : Boolean;
  102. End;
  103. implementation
  104. { TPCSafeboxRootHash }
  105. class function TPCSafeboxRootHash.CalcNextLevelHash(ABlocksHashBuffer: TBytesBuffer; AStartIndex, ABlocksCount: Integer; ANextLevel: TBytesBuffer): Boolean;
  106. var
  107. i, LLeft, LRight : Integer;
  108. LPByte : PByte;
  109. LSHA256 : TRawBytes;
  110. LTotalBlocks : Integer;
  111. begin
  112. Assert((ABlocksHashBuffer.Length MOD 32)=0,'ABlocksHashBuffer invalid length ('+IntToStr(ABlocksHashBuffer.Length)+') not modulo 32 = 0');
  113. Assert((AStartIndex>=0) And (ABlocksCount>0) And (ABlocksHashBuffer.Length>0),Format('Invalid AStartIndex:%d or ACount:%d or Length:%d',[AStartIndex,ABlocksCount,ABlocksHashBuffer.Length]));
  114. LTotalBlocks := ABlocksHashBuffer.Length DIV 32;
  115. ANextLevel.Clear;
  116. if LTotalBlocks=1 then begin
  117. ANextLevel.CopyFrom(ABlocksHashBuffer);
  118. Exit(True);
  119. end;
  120. if (AStartIndex + ABlocksCount)>LTotalBlocks then Exit(False); // Invalid params
  121. for i := 0 to ((LTotalBlocks-1) DIV 2) do begin
  122. LLeft := i*64;
  123. LRight := (i+1)*64;
  124. LPByte := ABlocksHashBuffer.Memory;
  125. Inc(LPByte,LLeft);
  126. if (LRight>ABlocksHashBuffer.Length) then begin
  127. ANextLevel.Add(LPByte^,32);
  128. end else begin
  129. LSHA256 := TCrypto.DoSha256(PAnsiChar(LPByte),64);
  130. ANextLevel.Add(LSHA256);
  131. end;
  132. end;
  133. Result := True;
  134. end;
  135. class function TPCSafeboxRootHash.CalcSafeBoxRootHash(ABlocksHashBuffer: TBytesBuffer): TRawBytes;
  136. begin
  137. Result := CalcSafeBoxRootHash(ABlocksHashBuffer,0,ABlocksHashBuffer.Length DIV 32);
  138. end;
  139. class function TPCSafeboxRootHash.CalcSafeBoxRootHash(ABlocksHashBuffer: TBytesBuffer; AStartIndex, ABlocksCount: Integer): TRawBytes;
  140. // PRE: The ABlocksHashBuffer has a length MODULO 32 = 0 and size > 0, because contains X blocks of 32 bytes each
  141. // each 32 bytes of ABlocksHashBuffer contains a SHA256 of TBlockAccount, extracted from TBlockAccount.block_hash
  142. function CalculateSafeBoxRootHash(APreviousLevelBuffer : TBytesBuffer) : TRawBytes;
  143. // PRE: APreviousLevelBuffer contains a set of 32 bytes
  144. var LNextLevel : TBytesBuffer;
  145. i, LLeft, LRight : Integer;
  146. LPByte : PByte;
  147. LSHA256 : TRawBytes;
  148. LTotalBlocks : Integer;
  149. begin
  150. LTotalBlocks := APreviousLevelBuffer.Length DIV 32;
  151. if (LTotalBlocks)=1 then begin
  152. SetLength(Result,32);
  153. Move(APreviousLevelBuffer.Memory^,Result[0],32);
  154. Exit;
  155. end;
  156. LNextLevel := TBytesBuffer.Create(APreviousLevelBuffer.Length DIV 2);
  157. try
  158. for i := 0 to ((LTotalBlocks-1) DIV 2) do begin
  159. LLeft := i*64;
  160. LRight := (i+1)*64;
  161. LPByte := APreviousLevelBuffer.Memory;
  162. Inc(LPByte,LLeft);
  163. if (LRight>APreviousLevelBuffer.Length) then begin
  164. LNextLevel.Add(LPByte^,32);
  165. end else begin
  166. LSHA256 := TCrypto.DoSha256(PAnsiChar(LPByte),64);
  167. LNextLevel.Add(LSHA256);
  168. end;
  169. end;
  170. Result := CalculateSafeBoxRootHash(LNextLevel)
  171. finally
  172. LNextLevel.Free;
  173. end;
  174. end;
  175. var LHashBufferChunk : TBytesBuffer;
  176. LTotalBlocks : Integer;
  177. LPByte : PByte;
  178. begin
  179. // Protection
  180. Assert((ABlocksHashBuffer.Length MOD 32)=0,'ABlocksHashBuffer invalid length ('+IntToStr(ABlocksHashBuffer.Length)+') not modulo 32 = 0');
  181. Assert((AStartIndex>=0) And (ABlocksCount>0) And (ABlocksHashBuffer.Length>0),Format('Invalid AStartIndex:%d or ACount:%d or Length:%d',[AStartIndex,ABlocksCount,ABlocksHashBuffer.Length]));
  182. LTotalBlocks := ABlocksHashBuffer.Length DIV 32;
  183. Assert((AStartIndex + ABlocksCount)<=LTotalBlocks,Format('Out of range AStartIndex:%d + ACount:%d > Blocks:%d',[AStartIndex,ABlocksCount,LTotalBlocks]));
  184. if (AStartIndex=0) And (ABlocksCount=LTotalBlocks) then begin
  185. Result := CalculateSafeBoxRootHash(ABlocksHashBuffer);
  186. end else begin
  187. LHashBufferChunk := TBytesBuffer.Create(LTotalBlocks*32);
  188. try
  189. LPByte := ABlocksHashBuffer.Memory;
  190. Inc(LPByte,32 * AStartIndex);
  191. LHashBufferChunk.Add(LPByte^, ABlocksCount*32);
  192. //
  193. Result := CalculateSafeBoxRootHash(LHashBufferChunk);
  194. finally
  195. LHashBufferChunk.Free;
  196. end;
  197. end;
  198. end;
  199. class function TPCSafeboxRootHash.CheckProof(ABlockIndex, ATotalBlocks: Integer; const AProofLevels: TProofLevels): Boolean;
  200. var iLevel : Integer;
  201. LLevelItemsCount : Integer;
  202. LLevelItemIndex : Integer;
  203. LRawToHash,
  204. LPreviousHashedValue : TRawBytes;
  205. begin
  206. // At least 1 level (single) or >2 levels where 0=leaf and Length-1 = RootHash
  207. if (Length(AProofLevels.Levels)=0) then Exit(False);
  208. if (Length(AProofLevels.Levels)=2) then Exit(False);
  209. Result := True;
  210. if (Length(AProofLevels.Levels)=1) then Exit(True); // If only 1 level, nothing to proof, is a single proof = True
  211. iLevel := 1;
  212. LLevelItemsCount := ATotalBlocks;
  213. LLevelItemIndex := ABlockIndex;
  214. SetLength(LRawToHash,32 * 2);
  215. LPreviousHashedValue := AProofLevels.Levels[0];
  216. while (iLevel<Length(AProofLevels.Levels)) do begin
  217. // Left or right?
  218. if (LLevelItemIndex MOD 2)=0 then begin
  219. // Even
  220. if (LLevelItemIndex+1<LLevelItemsCount) then begin
  221. Move(LPreviousHashedValue[0],LRawToHash[0],32);
  222. Move(AProofLevels.Levels[iLevel][0],LRawToHash[32],32);
  223. LPreviousHashedValue := TCrypto.DoSha256(LRawToHash);
  224. end
  225. else begin
  226. LPreviousHashedValue := Copy(LPreviousHashedValue);
  227. end;
  228. end else begin
  229. // Odd, always on right side
  230. Move(LPreviousHashedValue[0],LRawToHash[32],32);
  231. Move(AProofLevels.Levels[iLevel][0],LRawToHash[0],32);
  232. LPreviousHashedValue := TCrypto.DoSha256(LRawToHash);
  233. end;
  234. LLevelItemIndex := LLevelItemIndex DIV 2;
  235. LLevelItemsCount := ((LLevelItemsCount-1) DIV 2)+1;
  236. inc(iLevel);
  237. end;
  238. Result := TBaseType.Equals(LPreviousHashedValue,AProofLevels.Levels[High(AProofLevels.Levels)]);
  239. end;
  240. class function TPCSafeboxRootHash.GetProof(ABlocksHashBuffer: TBytesBuffer;
  241. ABlockIndex: Integer; var AProofLevels: TProofLevels): Boolean;
  242. // PRE: ABlockIndex is 0 indexed. Range 0..Total-1
  243. procedure AddToProofLevels(ABlockIndexToSave : Integer; const ABlocks : TBytesBuffer);
  244. var LPByte : PByte;
  245. LNewProof : TRawBytes;
  246. begin
  247. SetLength(LNewProof,32);
  248. LPByte := ABlocks.Memory;
  249. Inc(LPByte,ABlockIndexToSave * 32);
  250. Move(LPByte^,LNewProof[0],32);
  251. //
  252. SetLength(AProofLevels.Levels,Length(AProofLevels.Levels)+1);
  253. AProofLevels.Levels[High(AProofLevels.Levels)] := LNewProof;
  254. end;
  255. procedure GetLevelProof(APreviousLevelHashBuffer: TBytesBuffer; ALevelBlockIndex : Integer; var ALevels: TProofLevels);
  256. // PRE: At least we have 1 block
  257. var LTotalBlocks : Integer;
  258. LNextLevel : TBytesBuffer;
  259. begin
  260. LTotalBlocks := APreviousLevelHashBuffer.Length DIV 32;
  261. // Is top level?
  262. if LTotalBlocks=1 then begin
  263. // Include it at top
  264. AddToProofLevels(0, APreviousLevelHashBuffer);
  265. Exit;
  266. end;
  267. // Save current level
  268. // Even or Odd
  269. if (ALevelBlockIndex MOD 2)=0 then begin
  270. // Even = Left, put right one
  271. if ALevelBlockIndex+1<LTotalBlocks then begin
  272. AddToProofLevels(ALevelBlockIndex+1, APreviousLevelHashBuffer);
  273. end else begin
  274. // Last value, add itself
  275. AddToProofLevels(ALevelBlockIndex, APreviousLevelHashBuffer);
  276. end;
  277. end else begin
  278. // Odd = Right, put left one
  279. if (ALevelBlockIndex>0) then begin
  280. AddToProofLevels(ALevelBlockIndex-1, APreviousLevelHashBuffer);
  281. end else begin
  282. // First value, add itself
  283. AddToProofLevels(0, APreviousLevelHashBuffer);
  284. end;
  285. end;
  286. // Calculate next level
  287. LNextLevel := TBytesBuffer.Create(APreviousLevelHashBuffer.Length DIV 2);
  288. try
  289. CalcNextLevelHash(APreviousLevelHashBuffer,0,LTotalBlocks,LNextLevel);
  290. GetLevelProof(LNextLevel,(ALevelBlockIndex DIV 2),ALevels);
  291. finally
  292. LNextLevel.Free;
  293. end;
  294. end;
  295. var LTotalBlocks : Integer;
  296. begin
  297. // Protection
  298. Assert((ABlocksHashBuffer.Length MOD 32)=0,'ABlocksHashBuffer invalid length ('+IntToStr(ABlocksHashBuffer.Length)+') not modulo 32 = 0');
  299. // Init
  300. SetLength(AProofLevels.Levels,0);
  301. LTotalBlocks := ABlocksHashBuffer.Length DIV 32;
  302. Result := False;
  303. AProofLevels.Levels := Nil;
  304. if LTotalBlocks<=ABlockIndex then Exit(False);
  305. if LTotalBlocks=0 then Exit(False);
  306. // First
  307. Result := True;
  308. AddToProofLevels(ABlockIndex,ABlocksHashBuffer);
  309. if LTotalBlocks>1 then begin
  310. GetLevelProof(ABlocksHashBuffer,ABlockIndex,AProofLevels);
  311. end;
  312. end;
  313. { TBytesBuffer32Safebox }
  314. constructor TBytesBuffer32Safebox.Create(ADefaultIncrement: Integer);
  315. begin
  316. FNextLevelBytesBuffer := Nil;
  317. FSafeBoxHashCalcType := sbh_Single_Sha256;
  318. inherited;
  319. end;
  320. destructor TBytesBuffer32Safebox.Destroy;
  321. begin
  322. FreeAndNil(FNextLevelBytesBuffer);
  323. inherited;
  324. end;
  325. function TBytesBuffer32Safebox.GetSafeBoxHash: TRawBytes;
  326. begin
  327. if (FSafeBoxHashCalcType = sbh_Single_Sha256) then begin
  328. if ((Self.Length MOD 32)=0) and (Self.Length>0) then begin
  329. Result := TCrypto.DoSha256(Self.Memory,Self.Length);
  330. end else begin
  331. Result := Nil;
  332. end;
  333. end else if (Self.Length=32) then begin
  334. System.SetLength(Result,32);
  335. Move(Self.Memory^,Result[0],32);
  336. end else if (Self.Length>32) and ((Self.Length MOD 32)=0) then begin
  337. if Not Assigned(FNextLevelBytesBuffer) then begin
  338. RedoNextLevelsForMerkleRootHash;
  339. end;
  340. Result := FNextLevelBytesBuffer.GetSafeBoxHash;
  341. end else begin
  342. Result := Nil;
  343. end;
  344. end;
  345. procedure TBytesBuffer32Safebox.NotifyUpdated(AStartPos, ACountBytes: Integer);
  346. var LLevelItemIndex, LLevelItemsCount : Integer;
  347. LPByte : PByte;
  348. LSHA256 : TRawBytes;
  349. begin
  350. inherited;
  351. if (FSafeBoxHashCalcType = sbh_Single_Sha256) or
  352. ((ACountBytes<>32) or ((AStartPos MOD 32)<>0)) or (Self.Length<64) or ((Self.Length MOD 32)<>0) then begin
  353. FreeAndNil(FNextLevelBytesBuffer);
  354. end else if Not Assigned(FNextLevelBytesBuffer) then begin
  355. // First time must "Redo"
  356. RedoNextLevelsForMerkleRootHash;
  357. end else begin
  358. LLevelItemIndex := AStartPos DIV 32;
  359. LLevelItemsCount := Self.Length DIV 32;
  360. LPByte := Self.Memory;
  361. inc(LPByte,AStartPos);
  362. // Left or right?
  363. if (LLevelItemIndex MOD 2)=0 then begin
  364. // Even, we are Left
  365. if (LLevelItemIndex+1<LLevelItemsCount) then begin
  366. LSHA256 := TCrypto.DoSha256(PAnsiChar(LPByte),64);
  367. FNextLevelBytesBuffer.Replace((AStartPos DIV 2),LSHA256);
  368. end
  369. else begin
  370. // No sheet on right, same value on next level
  371. FNextLevelBytesBuffer.Replace(AStartPos DIV 2,LPByte^,32);
  372. end;
  373. end else begin
  374. // Odd, is on right side
  375. Dec(LPByte,32);
  376. LSHA256 := TCrypto.DoSha256(PAnsiChar(LPByte),64);
  377. FNextLevelBytesBuffer.Replace(((AStartPos-32) DIV 2),LSHA256);
  378. end;
  379. end;
  380. end;
  381. procedure TBytesBuffer32Safebox.RedoNextLevelsForMerkleRootHash;
  382. var i, j : Integer;
  383. begin
  384. if (Self.Length<64) or ((Self.Length MOD 32)<>0) then begin
  385. FreeAndNil(FNextLevelBytesBuffer);
  386. Exit;
  387. end;
  388. if Not Assigned(FNextLevelBytesBuffer) then begin
  389. FNextLevelBytesBuffer := TBytesBuffer32Safebox.Create(32*1000);
  390. FNextLevelBytesBuffer.SafeBoxHashCalcType := Self.SafeBoxHashCalcType;
  391. end;
  392. j := Self.Length DIV 64;
  393. for i := 0 to ((Self.Length DIV 64)-1) do begin
  394. NotifyUpdated( (i*64), 32);
  395. end;
  396. end;
  397. procedure TBytesBuffer32Safebox.SetSafeBoxHashCalcType(const Value: TSafeboxHashCalcType);
  398. begin
  399. if FSafeBoxHashCalcType=Value then Exit;
  400. FSafeBoxHashCalcType := Value;
  401. FreeAndNil(FNextLevelBytesBuffer);
  402. end;
  403. end.