diff --git a/examples/src/decodeData.js b/examples/src/decodeData.js index 58bacaf4..49933a74 100644 --- a/examples/src/decodeData.js +++ b/examples/src/decodeData.js @@ -5,6 +5,29 @@ import { getInstance } from './instantiation.js'; const myERC725 = getInstance(); const decodedDataOneKey = myERC725.decodeData([ + { + keyName: 'LSP3Profile', + value: + '0x00006f357c6a0020820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', + }, +]); +/** +[ + { + name: 'LSP3Profile', + key: '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5', + value: { + verification: { + method: 'keccak256(utf8)', + data: '0x820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361', + }, + url: 'ipfs://QmYr1VJLwerg6pEoscdhVGugo39pa6rycEZLjtRPDfW84UAx' + } + } +] +*/ + +const decodedDataOneKeyOld = myERC725.decodeData([ { keyName: 'LSP3Profile', value: @@ -31,7 +54,7 @@ const decodedDataManyKeys = myERC725.decodeData([ { keyName: 'LSP3Profile', value: - '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', + '0x00006f357c6a0020820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', }, { keyName: 'LSP3IssuedAssets[]', @@ -76,10 +99,14 @@ const decodedDataManyKeys = myERC725.decodeData([ ] */ console.log('/*--------------------------------------------/*'); -console.log('/* decodeData - one key /*'); +console.log('/* decodeData - one key (VerifiableURL) /*'); console.log('/*--------------------------------------------/*'); console.log(decodedDataOneKey); console.log('/*--------------------------------------------/*'); +console.log('/* decodeData - one key (JSONURL) /*'); +console.log('/*--------------------------------------------/*'); +console.log(decodedDataOneKeyOld); +console.log('/*--------------------------------------------/*'); console.log('/* decodeData - many keys /*'); console.log('/*--------------------------------------------/*'); console.log(decodedDataManyKeys); diff --git a/examples/src/encodeData.js b/examples/src/encodeData.js index 16984f41..138b8aa1 100644 --- a/examples/src/encodeData.js +++ b/examples/src/encodeData.js @@ -38,7 +38,7 @@ const encodedDataOneKeyV2 = myERC725.encodeData({ '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5' ], values: [ - '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178' + '0x00006f357c6a0020820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178' ] } */ @@ -76,7 +76,7 @@ const encodedDataManyKeys = myERC725.encodeData([ '0x0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b47' ], values: [ - '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', + '0x00006f357c6a0020820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', '0x0000000000000000000000000000000000000000000000000000000000000002', '0xd94353d9b005b3c0a9da169b768a31c57844e490', '0xdaea594e385fc724449e3118b2db7e86dfba1826', diff --git a/src/lib/decodeData.test.ts b/src/lib/decodeData.test.ts index 490fb5e7..3c004811 100644 --- a/src/lib/decodeData.test.ts +++ b/src/lib/decodeData.test.ts @@ -38,17 +38,31 @@ describe('decodeData', () => { { name: 'MyKeyName::', key: '0x', - keyType: 'Singleton', + keyType: 'MappingWithGrouping', valueType: 'bytes', valueContent: 'JSONURL', }, + { + name: 'MyKeyName2::', + key: '0x', + keyType: 'MappingWithGrouping', + valueType: 'bytes', + valueContent: 'VerifiableURL', + }, { name: 'MyDynamicKey:
', key: '0x', - keyType: 'Singleton', + keyType: 'Mapping', valueType: 'bytes', valueContent: 'JSONURL', }, + { + name: 'MyDynamicKey2:
', + key: '0x', + keyType: 'Mapping', + valueType: 'bytes', + valueContent: 'VerifiableURL', + }, ]; it('decodes each key', () => { @@ -160,6 +174,21 @@ describe('decodeData', () => { value: '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', }, + { + keyName: 'MyKeyName2::', + dynamicKeyParts: [ + '0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa', + 'true', + ], + value: + '0x00006f357c6a0020820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', + }, + { + keyName: 'MyDynamicKey2:
', + dynamicKeyParts: '0xcafecafecafecafecafecafecafecafecafecafe', + value: + '0x00006f357c6a0020820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', + }, { keyName: 'KeyTwo', value: '0x2222', @@ -171,6 +200,8 @@ describe('decodeData', () => { expect(decodedData.map(({ name }) => name)).to.eql([ 'MyKeyName:aaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa:true', 'MyDynamicKey:cafecafecafecafecafecafecafecafecafecafe', + 'MyKeyName2:aaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa:true', + 'MyDynamicKey2:cafecafecafecafecafecafecafecafecafecafe', 'KeyTwo', ]); }); diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index 4389821e..a9c361be 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -16,7 +16,14 @@ import { expect, assert } from 'chai'; -import { keccak256, utf8ToHex, stripHexPrefix, toBN, toHex } from 'web3-utils'; +import { + keccak256, + utf8ToHex, + stripHexPrefix, + toBN, + toHex, + padLeft, +} from 'web3-utils'; import { valueContentEncodingMap, encodeValueType, @@ -899,7 +906,7 @@ describe('encoder', () => { url: 'http://test.com/asset.glb', }, encodedValue: - '0x6f357c6a027547537d35728a741470df1ccf65de10b454ca0def7c5c20b257b7b8d16168687474703a2f2f746573742e636f6d2f61737365742e676c62', + '0x00006f357c6a0020027547537d35728a741470df1ccf65de10b454ca0def7c5c20b257b7b8d16168687474703a2f2f746573742e636f6d2f61737365742e676c62', }, { valueContent: 'BitArray', @@ -959,7 +966,9 @@ describe('encoder', () => { ).substring(2); assert.deepStrictEqual( encodedValue, - verificationMethod + jsonVerificationData + hexUrl, + `0x0000${stripHexPrefix(verificationMethod)}${stripHexPrefix( + padLeft(jsonVerificationData.length / 2, 4), + )}${jsonVerificationData}${hexUrl}`, ); const expectedDecodedValue: URLDataWithHash = { diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 6696cef9..99f6e8c9 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -74,23 +74,58 @@ const encodeDataSourceWithHash = ( const verificationMethod = getVerificationMethod( verification?.method || UNKNOWN_VERIFICATION_METHOD, ); - return ( - (verificationMethod - ? keccak256(verificationMethod.name).slice(0, 10) - : padLeft(0, 8)) + - stripHexPrefix(verification ? verification.data : padLeft(0, 64)) + - stripHexPrefix(utf8ToHex(dataSource)) - ); + return [ + padLeft(0, 4), + stripHexPrefix( + verificationMethod + ? padLeft(keccak256(verificationMethod.name).slice(0, 10), 8) + : padLeft(0, 8), + ), + stripHexPrefix( + verification?.data + ? padLeft(verification.data.slice(2).length / 2, 4) + : padLeft(0, 4), + ), + stripHexPrefix( + verification?.data ? stripHexPrefix(verification?.data) : '', + ), + stripHexPrefix(utf8ToHex(dataSource)), + ].join(''); }; const decodeDataSourceWithHash = (value: string): URLDataWithHash => { + if (value.slice(0, 6) === '0x0000') { + /* + 0 1 2 3 4 5 6 7 8 + 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + 0x0000 code + 6f357c6a hash fn [6] + 0020 data len [14] + 820464ddfac1be...[18 + data len] + [18 + data len]...696670733a2f2...[...rest] + */ + const verificationMethodSig = `0x${value.slice(6, 14)}`; + const verificationMethod = getVerificationMethod(verificationMethodSig); + if (verificationMethod !== undefined) { + const encodedLength = `0x${value.slice(14, 18)}`; // Rest of data string after function hash + const dataLength = hexToNumber(encodedLength, false) as number; + const dataHash = `0x${value.slice(18, 18 + dataLength * 2)}`; // Get jsonHash 32 bytes + const dataSource = hexToUtf8('0x' + value.slice(18 + dataLength * 2)); // Get remainder as URI + return { + verification: { + method: verificationMethod.name, + data: dataHash, + }, + url: dataSource, + }; + } + } + const verificationMethodSig = value.slice(0, 10); const verificationMethod = getVerificationMethod(verificationMethodSig); - - const encodedData = value.replace('0x', '').slice(8); // Rest of data string after function hash + const encodedData = value.slice(10); // Rest of data string after function hash const dataHash = '0x' + encodedData.slice(0, 64); // Get jsonHash 32 bytes const dataSource = hexToUtf8('0x' + encodedData.slice(64)); // Get remainder as URI - return { verification: { method: verificationMethod?.name || UNKNOWN_VERIFICATION_METHOD, @@ -648,7 +683,8 @@ export const valueContentEncodingMap = ( }; } // https://github.com/lukso-network/LIPs/blob/master/LSPs/LSP-2-ERC725YJSONSchema.md#jsonurl - case 'JSONURL': { + case 'JSONURL': + case 'VerifiableURL': { return { type: 'custom', encode: (dataToEncode: JSONURLDataToEncode) => { diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index ead0cace..4aac840c 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -44,14 +44,14 @@ export const getDataFromExternalSources = ( } if ( - !['jsonurl', 'asseturl'].includes( + !['jsonurl', 'asseturl', 'verifiableurl'].includes( schemaElement.valueContent.toLowerCase(), ) ) { return dataEntry; } - // At this stage, value should be of type jsonurl or asseturl + // At this stage, value should be of type jsonurl, verifiableurl or asseturl if (typeof dataEntry.value === 'string') { console.error( `Value of key: ${dataEntry.name} (${dataEntry.value}) is string but valueContent is: ${schemaElement.valueContent}. Expected type should be object with url key.`, diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index e583a000..205651c9 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -105,12 +105,12 @@ describe('utils', () => { { key: '0x9985edaf12cbacf5ac7d6ed54f0445cc00000000000000000000000000000000', value: - '0x6f357c6a733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', }, { key: '0x9985edaf12cbacf5ac7d6ed54f0445cc00000000000000000000000000000001', value: - '0x6f357c6a81bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', + '0x00006f357c6a002081bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', }, ], }, @@ -317,7 +317,7 @@ describe('utils', () => { url: 'http://day.night/asset.glb', }, encodedValue: - '0x6f357c6a81dadadadadadadadadadadadadadadf00a4bdfa8fcaf3791d25f69b497abf88687474703a2f2f6461792e6e696768742f61737365742e676c62', + '0x00006f357c6a002081dadadadadadadadadadadadadadadf00a4bdfa8fcaf3791d25f69b497abf88687474703a2f2f6461792e6e696768742f61737365742e676c62', }, { valueContent: 'JSONURL', @@ -330,7 +330,7 @@ describe('utils', () => { url: 'ipfs://QmbErKh3Fjsxxxxxxxxxxxxxxxxxxxxxxxxxxv9AJJvZbd', }, encodedValue: - '0x6f357c6a81bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', + '0x00006f357c6a002081bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', }, { @@ -643,7 +643,7 @@ describe('utils', () => { '0x0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b47', ], values: [ - '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', + '0x00006f357c6a0020820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361697066733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', '0x00000000000000000000000000000002', '0xd94353d9b005b3c0a9da169b768a31c57844e490', '0xdaea594e385fc724449e3118b2db7e86dfba1826', diff --git a/test/mockSchema.ts b/test/mockSchema.ts index 2784a728..ffd8829f 100644 --- a/test/mockSchema.ts +++ b/test/mockSchema.ts @@ -39,13 +39,13 @@ export const mockSchema: (ERC725JSONSchema & { // Testing data returnRawData: abiCoder.encodeParameter( 'bytes', - '0x6f357c6a733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', ), returnRawDataArray: abiCoder.encodeParameter('bytes[]', [ - '0x6f357c6a733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', ]), returnGraphData: - '0x6f357c6a733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', expectedResult: { verification: { method: 'keccak256(utf8)', @@ -65,13 +65,13 @@ export const mockSchema: (ERC725JSONSchema & { // Testing data returnRawData: abiCoder.encodeParameter( 'bytes', - '0x6f357c6aa7d9a84b44013f71356d72e6c15fdc2533c573271c53d053ed8ddcdaa60f4c81697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020a7d9a84b44013f71356d72e6c15fdc2533c573271c53d053ed8ddcdaa60f4c81697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', ), returnRawDataArray: abiCoder.encodeParameter('bytes[]', [ - '0x6f357c6aa7d9a84b44013f71356d72e6c15fdc2533c573271c53d053ed8ddcdaa60f4c81697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020a7d9a84b44013f71356d72e6c15fdc2533c573271c53d053ed8ddcdaa60f4c81697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', ]), returnGraphData: - '0x6f357c6aa7d9a84b44013f71356d72e6c15fdc2533c573271c53d053ed8ddcdaa60f4c81697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020a7d9a84b44013f71356d72e6c15fdc2533c573271c53d053ed8ddcdaa60f4c81697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', expectedResult: { verification: { method: 'keccak256(utf8)', @@ -263,26 +263,26 @@ export const mockSchema: (ERC725JSONSchema & { abiCoder.encodeParameter('bytes', leftPad(2, 32)), // array length abiCoder.encodeParameter( 'bytes', - '0x6f357c6a733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', ), abiCoder.encodeParameter( 'bytes', - '0x6f357c6a81bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', + '0x00006f357c6a002081bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', ), ], returnRawDataArray: [ abiCoder.encodeParameter('bytes[]', [leftPad(2, 32)]), abiCoder.encodeParameter('bytes[]', [ - '0x6f357c6a733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a0020733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', ]), abiCoder.encodeParameter('bytes[]', [ - '0x6f357c6a81bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', + '0x00006f357c6a002081bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', ]), ], returnGraphData: [ leftPad(2, 32), // array length - '0x6f357c6a733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', - '0x6f357c6a81bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', + '0x00006f357c6a0020733e78f2fc4a3304c141e8424d02c9069fe08950c6514b27289ead8ef4faa49d697066733a2f2f516d6245724b6833466a73415236596a73546a485a4e6d364d6344703661527438324674637639414a4a765a6264', + '0x00006f357c6a002081bd0b7ed5ac354abbf24619ce16933f00a4bdfa8fcaf3791d25f69b497abf88697066733a2f2f516d6245724b6833466a7378787878787878787878787878787878787878787878787878787639414a4a765a6264', ], expectedResult: [ // This JSON from JSONURL above...