Skip to content

Commit

Permalink
Low entropy token ID with 999999999999999 max (#987) (#990)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikiquantum authored Apr 17, 2019
1 parent 2a9356f commit 1adb44c
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 8 deletions.
2 changes: 1 addition & 1 deletion config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions nft/eth_invoice_unpaid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
13 changes: 8 additions & 5 deletions nft/invoice_unpaid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}

Expand Down
17 changes: 17 additions & 0 deletions utils/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
44 changes: 44 additions & 0 deletions utils/tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package utils

import (
"encoding/binary"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -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)
}
}
})
}
}

0 comments on commit 1adb44c

Please sign in to comment.