diff --git a/src/Domain.js b/src/Domain.js index 3651912..9371c8d 100644 --- a/src/Domain.js +++ b/src/Domain.js @@ -199,6 +199,9 @@ EIP712Domain.fromSignatureRequest = function fromSignatureRequest(request) { const dfs = type => { for (const {type: subtype} of types[type]) { if (marked.has(subtype) || isNotStructureType(subtype)) continue + if (isArrayType(subtype)){ + subtype = subtype.substring(0,subtype.indexOf("[")) + } if (cyclecheck.has(subtype)) { throw new Error('Cannot construct domain from signature request with cyclic dependencies') } diff --git a/src/Type.js b/src/Type.js index b500a10..a6b7231 100644 --- a/src/Type.js +++ b/src/Type.js @@ -120,6 +120,24 @@ export default function Type (primaryType, defs) { } } + encodeField(type, value){ + if (isDynamicType(type)){ + return {type:'bytes32', value:Buffer.from(keccak256(value), 'hex')} + } else if (type in domain.types){ + // Structure Types are recursively encoded and hashed + return {type:"bytes32", value: Buffer.from(keccak256(value.encodeData()), 'hex')} + } else if (isArrayType(type)){ + // Array types are the hash of their encoded members concatenated + var innerType = type.slice(0, type.lastIndexOf('[')); + var encodedValues = value.map(item => this.encodeField(innerType, item)); + return {type:'bytes32', value: Buffer.from(keccak256(abi.rawEncode(encodedValues.map(v=>v.type), encodedValues.map(v=>v.value))), 'hex')} + } else if (isAtomicType(type)){ + return {type: type, value:value} + } else { + throw new Error(`Unknown type: ${type}`) + } + } + /** * @override * Return the EIP712 data encoding of this instance, padding each member @@ -135,24 +153,9 @@ export default function Type (primaryType, defs) { let values = [Buffer.from(this.constructor.typeHash(), 'hex')] for (const {type, name} of this.constructor.properties) { - if (isDynamicType(type)) { - // Dynamic types are hashed - types.push('bytes32') - values.push(Buffer.from(keccak256(this[name]), 'hex')) - } else if (type in domain.types) { - // Structure Types are recursively encoded and hashed - types.push('bytes32') - values.push(Buffer.from(keccak256(this[name].encodeData()), 'hex')) - } else if (isArrayType(type)) { - // TODO: Figure out the spec for encoding array types - throw new Error('[DEV] Array types not yet supported') - } else if (isAtomicType(type)) { - // Atomic types have their encoding defined by the solidity ABI - types.push(type) - values.push(this[name]) - } else { - throw new Error(`Unknown type: ${type}`) - } + var {type:t,value:v} = this.encodeField(type, this[name]) + types.push(t) + values.push(v) } return abi.rawEncode(types, values) @@ -227,6 +230,11 @@ function validateTypeDefinition({name, type}, domain) { if (typeof type === 'object') { // TODO: Allow recursive type defintions? throw new Error('Nested type definitions not supported') + } else if (isArrayType(type)){ + var primitiveType = type.substring(0, type.lastIndexOf("[")); + if (!primitiveType in domain.types){ + throw new Error(`Type ${primitiveType} is undefined in this domain`) + } } else if (!isPrimitiveType(type) && !(type in domain.types)) { // Refuse undefined, non-primitive types throw new Error(`Type ${type} is undefined in this domain`) @@ -246,9 +254,15 @@ function validateTypeDefinition({name, type}, domain) { function findDependencies(props, domain, found=[]) { for (let {type} of props) { if (isPrimitiveType(type)) continue + var dependentTypeName = type + if (isArrayType(type)){ + dependentTypeName = type.substring(0, type.lastIndexOf("[")); + if (isPrimitiveType(dependentTypeName)) continue + } + // Merge the found array with new dependencies of found = found.concat( - [type, ...findDependencies(domain.types[type].properties, domain, found)] + [dependentTypeName, ...findDependencies(domain.types[dependentTypeName].properties, domain, found)] .filter(t => !found.includes(t)) ) } diff --git a/src/__tests__/data/Mail.json b/src/__tests__/data/Mail.json index e712cf8..5d6a413 100644 --- a/src/__tests__/data/Mail.json +++ b/src/__tests__/data/Mail.json @@ -13,7 +13,7 @@ ], "Mail": [ { "name": "from", "type": "Person" }, - { "name": "to", "type": "Person" }, + { "name": "to", "type": "Person[]" }, { "name": "contents", "type": "string" } ] }, @@ -29,20 +29,20 @@ "name": "Cow", "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" }, - "to": { + "to": [{ "name": "Bob", "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, + }], "contents": "Hello, Bob!" } }, "results": { "Mail": { - "encodeType": "Mail(Person from,Person to,string contents)Person(string name,address wallet)", - "typeHash": "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2", - "encodeData": "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8", - "hashStruct": "0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e", - "signHash": "0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2" + "encodeType": "Mail(Person from,Person[] to,string contents)Person(string name,address wallet)", + "typeHash": "0xdd57d9596af52b430ced3d5b52d4e3d5dccfdf3e0572db1dcf526baad311fbd1", + "encodeData": "0xdd57d9596af52b430ced3d5b52d4e3d5dccfdf3e0572db1dcf526baad311fbd1fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8e2ec7c01837071382eadbfdffa6c4c47de741fa0fda906874da49c4192dbe6edb5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8", + "hashStruct": "0xb737d8ce302eba53138b4a278391f7174db4fe0efb501e813572ee339d5e7751", + "signHash": "0xe3e147d255f92f5a9de155defefb2816b1ca970e975b38baadb0cca27e733d36" }, "domainSeparator": "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" }