diff --git a/config/configuration.go b/config/configuration.go index f1a7f3524..7e8167002 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -137,7 +137,7 @@ type Configuration interface { // GetLowEntropyNFTTokenEnabled enables low entropy token IDs. // The Dharma NFT Collateralizer and other contracts require tokenIds that are shorter than - // the ERC721 standard bytes32. This option reduces the length of the tokenId to 7 bytes. + // the ERC721 standard bytes32. This option reduces the maximum value of the tokenId. // There are security implications of doing this. Specifically the risk of two users picking the // same token id and minting it at the same time goes up and it theoretically could lead to a loss of an // NFT with large enough NFTRegistries (>100'000 tokens). It is not recommended to use this option. diff --git a/nft/eth_invoice_unpaid.go b/nft/eth_invoice_unpaid.go index c7b30da06..822b5c547 100644 --- a/nft/eth_invoice_unpaid.go +++ b/nft/eth_invoice_unpaid.go @@ -170,8 +170,8 @@ func (s *ethInvoiceUnpaid) MintNFT(ctx context.Context, req MintNFTRequest) (*Mi tokenID := NewTokenID() if s.cfg.GetLowEntropyNFTTokenEnabled() { - log.Warningf("Security consideration: Using only %d bit for NFT token ID generation. "+ - "Suggested course of action: disable by setting nft.lowentropy=false in config.yaml file", LowEntropyTokenIDLength*8) + log.Warningf("Security consideration: Using a reduced maximum of %s integer for NFT token ID generation. "+ + "Suggested course of action: disable by setting nft.lowentropy=false in config.yaml file", LowEntropyTokenIDMax) tokenID = NewLowEntropyTokenID() } diff --git a/nft/invoice_unpaid.go b/nft/invoice_unpaid.go index f612dd088..1d75337fe 100644 --- a/nft/invoice_unpaid.go +++ b/nft/invoice_unpaid.go @@ -14,8 +14,8 @@ const ( // TokenIDLength is the length of an NFT token ID TokenIDLength = 32 - // LowEntropyTokenIDLength is the length of a low entropy NFT token ID. Used only for special cases. - LowEntropyTokenIDLength = 7 + // LowEntropyTokenIDMax is the max of a low entropy NFT token ID big integer. Used only for special cases. + LowEntropyTokenIDMax = "999999999999999" ) // TokenID is uint256 in Solidity (256 bits | max value is 2^256-1) @@ -29,16 +29,19 @@ func NewTokenID() TokenID { return tid } -// NewLowEntropyTokenID returns a new low entropy(LowEntropyTokenIDLength) TokenID. +// NewLowEntropyTokenID returns a new low entropy(less than LowEntropyTokenIDMax) TokenID. // The Dharma NFT Collateralizer and other contracts require tokenIds that are shorter than -// the ERC721 standard bytes32. This option reduces the length of the tokenId to 7 bytes. +// the ERC721 standard bytes32. This option reduces the maximum integer of the tokenId to 999,999,999,999,999. // There are security implications of doing this. Specifically the risk of two users picking the // same token id and minting it at the same time goes up and it theoretically could lead to a loss of an // NFT with large enough NFTRegistries (>100'000 tokens). It is not recommended to use this option. func NewLowEntropyTokenID() TokenID { var tid [TokenIDLength]byte + // error is ignored here because the input is a constant. + n, _ := utils.RandomBigInt(LowEntropyTokenIDMax) + nByt := n.Bytes() // prefix with zeroes to match the bigendian big integer bytes for smart contract - copy(tid[:], append(make([]byte, TokenIDLength-LowEntropyTokenIDLength), utils.RandomSlice(LowEntropyTokenIDLength)...)) + copy(tid[:], append(make([]byte, TokenIDLength-len(nByt)), nByt...)) return tid } diff --git a/utils/tools.go b/utils/tools.go index a3255cc13..79b9112dd 100644 --- a/utils/tools.go +++ b/utils/tools.go @@ -213,3 +213,20 @@ func ConvertProofForEthereum(sortedHashes [][]byte) ([][32]byte, error) { return property, nil } + +// RandomBigInt returns a random big int that's less than the provided max. +func RandomBigInt(max string) (*big.Int, error) { + m := new(big.Int) + _, ok := m.SetString(max, 10) + if !ok { + return nil, errors.New("probably not a number %s", max) + } + + //Generate cryptographically strong pseudo-random between 0 - m + n, err := rand.Int(rand.Reader, m) + if err != nil { + return nil, err + } + + return n, nil +} diff --git a/utils/tools_test.go b/utils/tools_test.go index cb90aa28a..dbf9334b4 100644 --- a/utils/tools_test.go +++ b/utils/tools_test.go @@ -4,6 +4,7 @@ package utils import ( "encoding/binary" + "math/big" "testing" "github.com/ethereum/go-ethereum/common" @@ -251,3 +252,46 @@ func verifyHex(t *testing.T, val string) { _, err := hexutil.Decode(val) assert.Nil(t, err) } + +func TestRandomBigInt(t *testing.T) { + tests := []struct { + max string + isErr bool + }{ + { + "999", + false, + }, + { + "150", + false, + }, + { + "999999999999999", + false, + }, + { + "10000", + false, + }, + { + "323hu", + true, + }, + } + for _, test := range tests { + t.Run(test.max, func(t *testing.T) { + for i := 0; i < 100; i++ { + n, err := RandomBigInt(test.max) + if test.isErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + tt := new(big.Int) + tt.SetString(test.max, 10) + assert.True(t, n.Cmp(tt) <= 0) + } + } + }) + } +}