Parcourir la source

Merge pull request #72 from PascalCoinDev/master

PIP-0044 - Induplicatable NFT
Albert Molina il y a 2 ans
Parent
commit
7ff9a0457c

+ 1 - 0
CHANGELOG.md

@@ -4,6 +4,7 @@
 - MANDATORY UPGRADE - HARD FORK ACTIVATION WILL OCCUR ON BLOCK **(pending)**
 - MANDATORY UPGRADE - HARD FORK ACTIVATION WILL OCCUR ON BLOCK **(pending)**
 - Upgrade to Protocol 6 (Hard fork)
 - Upgrade to Protocol 6 (Hard fork)
 - Implementation of PIP-0043 (Update OP_RECOVER to recover only non used, not named PASA's) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0043.md
 - Implementation of PIP-0043 (Update OP_RECOVER to recover only non used, not named PASA's) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0043.md
+- Implementation of PIP-0044 (Induplicatable NFT) -> https://github.com/PascalCoin/PascalCoin/blob/master/PIP/PIP-0044.md
 - Improvements on downloading Safebox (fresh installation)
 - Improvements on downloading Safebox (fresh installation)
 
 
 
 

+ 64 - 0
PIP/PIP-0044.md

@@ -0,0 +1,64 @@
+<pre>
+  PIP: PIP-0044
+  Title: Induplicatable NFT
+  Type: Protocol
+  Impact: Hard-Fork
+  Author: Albert Molina <[email protected]>  
+  Copyright: Albert Molina, 2023 (All Rights Reserved)
+  Comments-URI: https://discord.gg/Scr8mcwnrC (Discord channel #pip-44)
+  Status: Proposed
+  Created: 2023-03-14
+</pre>
+
+## Summary
+
+NFT (Non-fungible-token) is a well known item in the blockchain industry. It's currently based on store the item (usually a HASH of a information) in the blockchain as a Proof-of-ownership of the item.
+
+This means that this HASH of the item is stored in a transaction included in a block, and what really is used for transfers (buy/sell transactions) is a reference of the transaction, so **there is no warranty/prevention that same NFT HASH is stored i other blocks/transactions**
+
+A true NFT must be something that is impossible to be duplicated, so we present a way to store Induplicatable NFT on the blockchain because the HASH will live on the Safebox struct (that is a representation of the ledger balance of the blockchain information)
+
+Also, thanks to Safebox current features, this Induplicatable NFT can be sold using same on-chain transactions mechanism without third party neither single point of failure (PIP-0002 - In-protocol PASA Exchange)
+
+## Proposal
+
+This PIP specifies how to use current Safebox struct and operations to store Induplicatable NFT on the PascalCoin blockchain
+
+- Safebox PASA's has Account Names and Types as described on PIP-0004, that allows to store unique Account Names in the safebox
+
+- Implementation of PIP-0004 limited Account Name to be Null or 3..64 characters long
+ 
+- Implementation of PIP-0004 prevents first char to be a number ('0'..'9') to not confuse name as an account number
+
+In order to use Account Name as a HASH, we must do one of proposals:
+
+- Without protocol upgrade:
+
+  - **Option A**: Use a "encode"/"decode" function to convert first char in a numeric/non-numeric char like convert "`ghijklmnop`" as "`0123456789`" for first char, so hash `9a737f6e41c58935c535fe7b08426006f246986810c21deeb808cc564b8ecdca` will be encoded to `pa737f6e41c58935c535fe7b08426006f246986810c21deeb808cc564b8ecdca` (transform 9 -> p)
+
+- With protocol upgrade (Hard fork):
+
+  - **Option B**: Start hash value with a suffix like "`nft_`" because first char cannot be an Hexadecimal numeric number, in this case the 64 characters length is a limitation because we cannot store 32 bytes hash plus suffix length in 64 chars
+
+  - **Option C**: Allows usage of numeric first char when name is a representation of a 32 bytes hexadecimal value
+
+
+This PIP-0044 will implement **Option C** allowing first char as a numberic "0".."9" char when name contains a 32 bytes (64 chars) hexadecimal value, this will prevent to use first number as an account number caused to overflow
+
+## Implementation
+
+```
+  // Update ValidAccountName function introducing this exception:
+  ...
+  if (new_name[0] in [Ord('0')..Ord('9')]) then
+    if (protocol_version>=CT_PROTOCOL_6) and
+       (length(new_name)=64) and
+       (IsHexadecimal(new_name))
+    then continue
+	else Error('Invalid numeric first char on a non-hash hexadecimal 32 bytes representation');
+  end;
+```
+
+## Backwards Compatibility
+
+This change is not backwards compatible and requires a hard-fork activation. 

+ 50 - 41
src/core/UAccounts.pas

@@ -58,6 +58,7 @@ Type
     class procedure GetRewardDistributionForNewBlock(const OperationBlock : TOperationBlock; out acc_0_miner_reward, acc_4_dev_reward : Int64; out acc_4_for_dev : Boolean);
     class procedure GetRewardDistributionForNewBlock(const OperationBlock : TOperationBlock; out acc_0_miner_reward, acc_4_dev_reward : Int64; out acc_4_for_dev : Boolean);
     class Function CalcSafeBoxHash(ABlocksHashBuffer : TBytesBuffer; protocol_version : Integer) : TRawBytes;
     class Function CalcSafeBoxHash(ABlocksHashBuffer : TBytesBuffer; protocol_version : Integer) : TRawBytes;
     class Function AllowUseHardcodedRandomHashTable(const AHardcodedFileName : String; const AHardcodedSha256Value : TRawBytes) : Boolean;
     class Function AllowUseHardcodedRandomHashTable(const AHardcodedFileName : String; const AHardcodedSha256Value : TRawBytes) : Boolean;
+    class function IsValidAccountName(protocol_version : Integer; const new_name : TRawBytes; var errors : String) : Boolean;
   end;
   end;
 
 
   TAccount_Helper = record helper for TAccount
   TAccount_Helper = record helper for TAccount
@@ -290,7 +291,6 @@ Type
     Procedure SaveSafeBoxToAStream(Stream : TStream; FromBlock, ToBlock : Cardinal);
     Procedure SaveSafeBoxToAStream(Stream : TStream; FromBlock, ToBlock : Cardinal);
     class Function CopySafeBoxStream(Source,Dest : TStream; FromBlock, ToBlock : Cardinal; var errors : String) : Boolean;
     class Function CopySafeBoxStream(Source,Dest : TStream; FromBlock, ToBlock : Cardinal; var errors : String) : Boolean;
     class Function ConcatSafeBoxStream(Source1, Source2, Dest : TStream; var errors : String) : Boolean;
     class Function ConcatSafeBoxStream(Source1, Source2, Dest : TStream; var errors : String) : Boolean;
-    class function ValidAccountName(const new_name : TRawBytes; var errors : String) : Boolean;
 
 
     Function IsValidNewOperationsBlock(Const newOperationBlock : TOperationBlock; checkSafeBoxHash, checkValidOperationsBlock : Boolean; var errors : String) : Boolean;
     Function IsValidNewOperationsBlock(Const newOperationBlock : TOperationBlock; checkSafeBoxHash, checkValidOperationsBlock : Boolean; var errors : String) : Boolean;
     class Function IsValidOperationBlock(Const newOperationBlock : TOperationBlock; var errors : String) : Boolean;
     class Function IsValidOperationBlock(Const newOperationBlock : TOperationBlock; var errors : String) : Boolean;
@@ -900,6 +900,53 @@ begin
   end;
   end;
 end;
 end;
 
 
+class function TPascalCoinProtocol.IsValidAccountName(protocol_version: Integer; const new_name: TRawBytes; var errors: String): Boolean;
+  { Note:
+    This function is case senstive, and only lower case chars are valid.
+    Execute a LowerCase() prior to call this function!
+    }
+Const CT_PascalCoin_Base64_Charset : RawByteString = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-+{}[]\_:"|<>,.?/~';
+      // First char can't start with a number
+      CT_PascalCoin_FirstChar_Charset : RawByteString = 'abcdefghijklmnopqrstuvwxyz!@#$%^&*()-+{}[]\_:"|<>,.?/~';
+      CT_PascalCoin_name_min_length = 3;
+      CT_PascalCoin_name_max_length = 64;
+var i,j : Integer;
+  Lraw : TRawBytes;
+begin
+  Result := False; errors := '';
+  if (length(new_name)<CT_PascalCoin_name_min_length) Or (length(new_name)>CT_PascalCoin_name_max_length) then begin
+    errors := 'Invalid length:'+IntToStr(Length(new_name))+' (valid from '+Inttostr(CT_PascalCoin_name_max_length)+' to '+IntToStr(CT_PascalCoin_name_max_length)+')';
+    Exit;
+  end;
+  for i:=Low(new_name) to High(new_name) do begin
+    if (i=Low(new_name)) then begin
+      j:=Low(CT_PascalCoin_FirstChar_Charset);
+      // First char can't start with a number
+      While (j<=High(CT_PascalCoin_FirstChar_Charset)) and (Ord(new_name[i])<>Ord(CT_PascalCoin_FirstChar_Charset[j])) do inc(j);
+      if (j>High(CT_PascalCoin_FirstChar_Charset)) then begin
+        // Allow Account Name as an hexadecimal value for a hash on Protocol V6 as proposed on PIP-0044
+        if Not (
+          (protocol_version>=CT_PROTOCOL_6) and
+          (new_name[i] in [Ord('0')..Ord('9')]) and
+          (length(new_name)=64) and
+          (TCrypto.HexaToRaw(new_name.ToString,Lraw))
+          ) then begin
+          errors := 'Invalid char '+Char(new_name[i])+' at first pos';
+          Exit; // Not found
+        end;
+      end;
+    end else begin
+      j:=Low(CT_PascalCoin_Base64_Charset);
+      While (j<=High(CT_PascalCoin_Base64_Charset)) and (Ord(new_name[i])<>Ord(CT_PascalCoin_Base64_Charset[j])) do inc(j);
+      if j>High(CT_PascalCoin_Base64_Charset) then begin
+        errors := 'Invalid char '+Char(new_name[i])+' at pos '+IntToStr(i);
+        Exit; // Not found
+      end;
+    end;
+  end;
+  Result := True;
+end;
+
 class function TPascalCoinProtocol.IsValidMinerBlockPayload(const newBlockPayload: TRawBytes): Boolean;
 class function TPascalCoinProtocol.IsValidMinerBlockPayload(const newBlockPayload: TRawBytes): Boolean;
 var i : Integer;
 var i : Integer;
 begin
 begin
@@ -3627,7 +3674,7 @@ begin
           //
           //
           // check valid
           // check valid
           If (Length(LBlock.accounts[iacc].name)>0) then begin
           If (Length(LBlock.accounts[iacc].name)>0) then begin
-            if Not TPCSafeBox.ValidAccountName(LBlock.accounts[iacc].name,aux_errors) then begin
+            if Not TPascalCoinProtocol.IsValidAccountName(CurrentProtocol,LBlock.accounts[iacc].name,aux_errors) then begin
               errors := errors + ' > Invalid name "'+LBlock.accounts[iacc].name.ToPrintable+'": '+aux_errors;
               errors := errors + ' > Invalid name "'+LBlock.accounts[iacc].name.ToPrintable+'": '+aux_errors;
               Exit;
               Exit;
             end;
             end;
@@ -4296,44 +4343,6 @@ begin
 end;
 end;
 
 
 
 
-class function TPCSafeBox.ValidAccountName(const new_name: TRawBytes; var errors : String): Boolean;
-  { Note:
-    This function is case senstive, and only lower case chars are valid.
-    Execute a LowerCase() prior to call this function!
-    }
-Const CT_PascalCoin_Base64_Charset : RawByteString = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-+{}[]\_:"|<>,.?/~';
-      // First char can't start with a number
-      CT_PascalCoin_FirstChar_Charset : RawByteString = 'abcdefghijklmnopqrstuvwxyz!@#$%^&*()-+{}[]\_:"|<>,.?/~';
-      CT_PascalCoin_name_min_length = 3;
-      CT_PascalCoin_name_max_length = 64;
-var i,j : Integer;
-begin
-  Result := False; errors := '';
-  if (length(new_name)<CT_PascalCoin_name_min_length) Or (length(new_name)>CT_PascalCoin_name_max_length) then begin
-    errors := 'Invalid length:'+IntToStr(Length(new_name))+' (valid from '+Inttostr(CT_PascalCoin_name_max_length)+' to '+IntToStr(CT_PascalCoin_name_max_length)+')';
-    Exit;
-  end;
-  for i:=Low(new_name) to High(new_name) do begin
-    if (i=Low(new_name)) then begin
-      j:=Low(CT_PascalCoin_FirstChar_Charset);
-      // First char can't start with a number
-      While (j<=High(CT_PascalCoin_FirstChar_Charset)) and (Ord(new_name[i])<>Ord(CT_PascalCoin_FirstChar_Charset[j])) do inc(j);
-      if j>High(CT_PascalCoin_FirstChar_Charset) then begin
-        errors := 'Invalid char '+Char(new_name[i])+' at first pos';
-        Exit; // Not found
-      end;
-    end else begin
-      j:=Low(CT_PascalCoin_Base64_Charset);
-      While (j<=High(CT_PascalCoin_Base64_Charset)) and (Ord(new_name[i])<>Ord(CT_PascalCoin_Base64_Charset[j])) do inc(j);
-      if j>High(CT_PascalCoin_Base64_Charset) then begin
-        errors := 'Invalid char '+Char(new_name[i])+' at pos '+IntToStr(i);
-        Exit; // Not found
-      end;
-    end;
-  end;
-  Result := True;
-end;
-
 var _initialSafeboxHash : TRawBytes = Nil;
 var _initialSafeboxHash : TRawBytes = Nil;
 
 
 class function TPCSafeBox.InitialSafeboxHash: TRawBytes;
 class function TPCSafeBox.InitialSafeboxHash: TRawBytes;
@@ -5643,7 +5652,7 @@ begin
   if (Not TBaseType.Equals(newName,P_target^.name)) then begin
   if (Not TBaseType.Equals(newName,P_target^.name)) then begin
     // NEW NAME CHANGE CHECK:
     // NEW NAME CHANGE CHECK:
     if Length(newName)>0 then begin
     if Length(newName)>0 then begin
-      If Not TPCSafeBox.ValidAccountName(newName,errors) then begin
+      If Not TPascalCoinProtocol.IsValidAccountName(FreezedSafeBox.CurrentProtocol,newName,errors) then begin
         errors := 'Invalid account name "'+newName.ToPrintable+'" length:'+IntToStr(length(newName))+': '+errors;
         errors := 'Invalid account name "'+newName.ToPrintable+'" length:'+IntToStr(length(newName))+': '+errors;
         Exit;
         Exit;
       end;
       end;

+ 1 - 1
src/core/UOpTransaction.pas

@@ -550,7 +550,7 @@ begin
   end;
   end;
   If (account_name in FData.changes_type) then begin
   If (account_name in FData.changes_type) then begin
     If (Length(FData.new_name)>0) then begin
     If (Length(FData.new_name)>0) then begin
-      If Not TPCSafeBox.ValidAccountName(FData.new_name,errors) then Exit;
+      If Not TPascalCoinProtocol.IsValidAccountName(AccountTransaction.FreezedSafeBox.CurrentProtocol,FData.new_name,errors) then Exit;
     end;
     end;
   end else begin
   end else begin
     If (Length(FData.new_name)>0) then begin
     If (Length(FData.new_name)>0) then begin

+ 1 - 1
src/core/UPCRPCFindAccounts.pas

@@ -206,7 +206,7 @@ begin
   // Validate Parameters
   // Validate Parameters
   if (Length(LAccountName)>0) And (LSearchByNameType = st_exact) then begin
   if (Length(LAccountName)>0) And (LSearchByNameType = st_exact) then begin
     LRaw.FromString( LAccountName );
     LRaw.FromString( LAccountName );
-    if not ASender.Node.Bank.SafeBox.ValidAccountName(LRaw, LErrors) then begin
+    if not TPascalCoinProtocol.IsValidAccountName(CT_BUILD_PROTOCOL, LRaw, LErrors) then begin
       AErrorNum := CT_RPC_ErrNum_InvalidAccountName;
       AErrorNum := CT_RPC_ErrNum_InvalidAccountName;
       AErrorDesc := LErrors;
       AErrorDesc := LErrors;
       exit;
       exit;

+ 1 - 1
src/core/UTxMultiOperation.pas

@@ -680,7 +680,7 @@ begin
     end;
     end;
     If (account_name in chi.changes_type) then begin
     If (account_name in chi.changes_type) then begin
       If (Length(chi.New_Name)>0) then begin
       If (Length(chi.New_Name)>0) then begin
-        If Not TPCSafeBox.ValidAccountName(chi.New_Name,errors) then Exit;
+        If Not TPascalCoinProtocol.IsValidAccountName(AccountTransaction.FreezedSafeBox.CurrentProtocol,chi.New_Name,errors) then Exit;
         // Check name not found!
         // Check name not found!
         j := AccountTransaction.FindAccountByNameInTransaction(chi.New_Name,newNameWasAdded, newNameWasDeleted);
         j := AccountTransaction.FindAccountByNameInTransaction(chi.New_Name,newNameWasAdded, newNameWasDeleted);
         If (j>=0) Or (newNameWasAdded) or (newNameWasDeleted) then begin
         If (j>=0) Or (newNameWasAdded) or (newNameWasDeleted) then begin

+ 1 - 1
src/gui-classic/UFRMOperation.pas

@@ -1050,7 +1050,7 @@ begin
       If Not TBaseType.Equals(newName,TargetAccount.name) then begin
       If Not TBaseType.Equals(newName,TargetAccount.name) then begin
         changeName:=True;
         changeName:=True;
         If Length(newName)>0 then begin
         If Length(newName)>0 then begin
-          if (Not TPCSafeBox.ValidAccountName(newName,errors)) then begin
+          if (Not TPascalCoinProtocol.IsValidAccountName(TNode.Node.Bank.SafeBox.CurrentProtocol,newName,errors)) then begin
             errors := '"'+newName.ToPrintable+'" is not a valid name: '+errors;
             errors := '"'+newName.ToPrintable+'" is not a valid name: '+errors;
             Exit;
             Exit;
           end;
           end;

+ 1 - 1
src/gui-classic/UFRMOperationsExplorer.pas

@@ -409,7 +409,7 @@ LBL_start_changer:
       aux := new_name;
       aux := new_name;
       If Not InputQuery(Caption,Format('New name for account %s:',[TAccountComp.AccountNumberToAccountTxtNumber(nAccount)]),aux) then Break;
       If Not InputQuery(Caption,Format('New name for account %s:',[TAccountComp.AccountNumberToAccountTxtNumber(nAccount)]),aux) then Break;
       aux := LowerCase(aux);
       aux := LowerCase(aux);
-    Until (aux='') Or (TPCSafeBox.ValidAccountName(TEncoding.ANSI.GetBytes(aux),errors));
+    Until (aux='') Or (TPascalCoinProtocol.IsValidAccountName(FSourceNode.Bank.SafeBox.CurrentProtocol,TEncoding.ANSI.GetBytes(aux),errors));
     new_name := aux;
     new_name := aux;
 
 
     aux := IntToStr(new_type);
     aux := IntToStr(new_type);