Skip to content

Commit 44a15c7

Browse files
Mintable NFT asset id computation in js.
1 parent 0f08e6c commit 44a15c7

File tree

2 files changed

+65
-32
lines changed

2 files changed

+65
-32
lines changed

committee/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
'aerospike==3.9.0',
99
'aioredis==1.2.0',
1010
'fastecdsa==1.7.2',
11-
'marshmallow-dataclass==6.0.0',
11+
'marshmallow-dataclass==7.1.0',
1212
'marshmallow==3.2.1',
1313
'PyYAML==5.1',
1414
'requests == 2.22.0',

crypto/starkware/crypto/signature/asset.js

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,15 @@ const assert = require('assert');
2323
// Generate BN of 1.
2424
const oneBn = new BN('1', 16);
2525

26-
// This number is used to shift the packed encoded asset information by 256 bits.
27-
const shiftBN = new BN('10000000000000000000000000000000000000000000000000000000000000000', 16);
28-
2926
// Used to mask the 251 least signifcant bits given by Keccack256 to produce the final asset ID.
3027
const mask = new BN('3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16);
3128

29+
// Used to mask the 240 least signifcant bits given by Keccack256 to produce the final asset ID
30+
// (for mintable assets).
31+
const mask240 = new BN('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16);
32+
const maskMintabilityBit =
33+
new BN('400000000000000000000000000000000000000000000000000000000000000', 16);
3234

33-
/*
34-
Computes the hash representing the asset ID for a given asset.
35-
asset is a dictionary containing the type and data of the asset to parse. the asset type is
36-
represented by a string describing the associated asset while the data is a dictionary
37-
containing further infomartion to distinguish between assets of a given type (such as the
38-
address of the smart contract of an ERC20 asset).
39-
The function returns the computed asset ID as a hex-string.
40-
41-
For example:
42-
43-
assetDict = {
44-
type: 'ERC20',
45-
data: { quantum: '10000', tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }
46-
}
47-
48-
Will produce an the following asset ID:
49-
50-
'0x352386d5b7c781d47ecd404765307d74edc4d43b0490b8e03c71ac7a7429653'.
51-
*/
5235
function getAssetType(assetDict) {
5336
const assetSelector = getAssetSelector(assetDict.type);
5437

@@ -64,17 +47,15 @@ function getAssetType(assetDict) {
6447
// In the case there is a valid tokenAddress in the data, we append that to the asset info
6548
// (before the quantum).
6649
const tokenAddress = new BN(encUtils.removeHexPrefix(assetDict.data.tokenAddress), 16);
67-
assetInfo = assetInfo.mul(shiftBN);
50+
assetInfo = assetInfo.ushln(256).add(tokenAddress);
6851
expectedLen += 64;
69-
assetInfo = assetInfo.add(tokenAddress);
7052
}
7153

7254
// Default quantum is 1 (for assets which don't specify quantum explicitly).
7355
const quantInfo = assetDict.data.quantum;
7456
const quantum = (quantInfo === undefined) ? oneBn : new BN(quantInfo, 10);
75-
assetInfo = assetInfo.mul(shiftBN);
57+
assetInfo = assetInfo.ushln(256).add(quantum);
7658
expectedLen += 64;
77-
assetInfo = assetInfo.add(quantum);
7859

7960
let assetType = sha3.keccak_256(
8061
encUtils.hexToBuffer(addLeadingZeroes(assetInfo.toJSON(), expectedLen))
@@ -85,23 +66,64 @@ function getAssetType(assetDict) {
8566
return '0x' + assetType.toJSON();
8667
}
8768

69+
/*
70+
Computes the hash representing the encoded asset ID for a given asset.
71+
asset is a dictionary containing the type and data of the asset to parse. the asset type is
72+
represented by a string describing the associated asset while the data is a dictionary
73+
containing further information to distinguish between assets of a given type (such as the
74+
address of the smart contract of an ERC20 asset).
75+
The function returns the computed asset ID as a hex-string.
76+
77+
For example:
78+
79+
assetDict = {
80+
type: 'ERC20',
81+
data: { quantum: '10000', tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }
82+
}
83+
84+
Will produce an the following asset ID:
85+
86+
'0x352386d5b7c781d47ecd404765307d74edc4d43b0490b8e03c71ac7a7429653'.
87+
*/
8888
function getAssetId(assetDict) {
8989
const assetType = new BN(encUtils.removeHexPrefix(getAssetType(assetDict)), 16);
9090
// For ETH and ERC20, the asset ID is simply the asset type.
9191
let assetId = assetType;
9292
if (assetDict.type === 'ERC721') {
9393
// ERC721 assets require a slightly different construction for asset info.
9494
let assetInfo = new BN(encUtils.utf8ToBuffer('NFT:'), 16);
95-
assetInfo = assetInfo.mul(shiftBN);
96-
assetInfo = assetInfo.add(assetType);
97-
assetInfo = assetInfo.mul(shiftBN);
98-
assetInfo = assetInfo.add(new BN(parseInt(assetDict.data.tokenId), 16));
99-
const expectedLen = 136;
95+
// ExpectedLen is equal to the length (in hex characters) of the appended strings:
96+
// 'NFT:' (8 characters), 'assetType' (64 characters), 'tokenId' (64 characters).
97+
// Where assetType and tokenId are each padded with 0's to account for 64 hex characters
98+
// each.
99+
// We use this in order to pad the final assetInfo string with leading zeros in case the
100+
// calculation discarded them in the process.
101+
const expectedLen = 8 + 64 + 64;
102+
assetInfo = assetInfo.ushln(256).add(assetType);
103+
assetInfo = assetInfo.ushln(256).add(new BN(parseInt(assetDict.data.tokenId), 16));
100104
assetId = sha3.keccak_256(
101105
encUtils.hexToBuffer(addLeadingZeroes(assetInfo.toJSON(), expectedLen))
102106
);
103107
assetId = new BN(assetId, 16);
104108
assetId = assetId.and(mask);
109+
} else if (assetDict.type === 'MINTABLE_ERC721' || assetDict.type === 'MINTABLE_ERC20') {
110+
let assetInfo = new BN(encUtils.utf8ToBuffer('MINTABLE:'), 16);
111+
// ExpectedLen is equal to the length (in hex characters) of the appended strings:
112+
// 'MINTABLE:' (18 characters), 'assetType' (64 characters), 'blobHash' (64 characters).
113+
// Where assetType and blobHash are each padded with 0's to account for 64 hex characters
114+
// each.
115+
// We use this in order to pad the final assetInfo string with leading zeros in case the
116+
// calculation discarded them in the process.
117+
const expectedLen = 18 + 64 + 64;
118+
assetInfo = assetInfo.ushln(256).add(assetType);
119+
const blobHash = blobToBlobHash(assetDict.data.blob);
120+
assetInfo = assetInfo.ushln(256).add(new BN(encUtils.removeHexPrefix(blobHash), 16));
121+
assetId = sha3.keccak_256(
122+
encUtils.hexToBuffer(addLeadingZeroes(assetInfo.toJSON(), expectedLen))
123+
);
124+
assetId = new BN(assetId, 16);
125+
assetId = assetId.and(mask240);
126+
assetId = assetId.or(maskMintabilityBit);
105127
}
106128

107129
return '0x' + assetId.toJSON();
@@ -122,6 +144,12 @@ function getAssetSelector(assetDictType) {
122144
case 'ERC721':
123145
seed = 'ERC721Token(address,uint256)';
124146
break;
147+
case 'MINTABLE_ERC20':
148+
seed = 'MintableERC20Token(address)';
149+
break;
150+
case 'MINTABLE_ERC721':
151+
seed = 'MintableERC721Token(address,uint256)';
152+
break;
125153
default:
126154
throw new Error(`Unknown token type: ${assetDictType}`);
127155
}
@@ -140,6 +168,11 @@ function addLeadingZeroes(hexStr, expectedLen) {
140168
return res;
141169
}
142170

171+
function blobToBlobHash(blob) {
172+
return '0x' + sha3.keccak_256(blob);
173+
}
174+
175+
143176
module.exports = {
144177
getAssetType,
145178
getAssetId // Function.

0 commit comments

Comments
 (0)