Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 86 additions & 26 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,77 @@ const TypedDataUtils = {
* @param {Object} types - Type definitions
* @returns {string} - Encoded representation of an object
*/
encodeData (primaryType, data, types) {
encodeData (primaryType, data, types, useV4 = true) {
const encodedTypes = ['bytes32']
const encodedValues = [this.hashType(primaryType, types)]

for (const field of types[primaryType]) {
let value = data[field.name]
if (value !== undefined) {
if (field.type === 'bytes') {
encodedTypes.push('bytes32')
value = ethUtil.sha3(value)
encodedValues.push(value)
} else if (field.type === 'string') {
encodedTypes.push('bytes32')
if(useV4) {
const encodeField = (name, type, value) => {
if (types[type] !== undefined) {
return ['bytes32', value == null ?
'0x0000000000000000000000000000000000000000000000000000000000000000' :
ethUtil.sha3(this.encodeData(type, value, types, useV4))]
}

if(value === undefined)
throw new Error(`missing value for field ${name} of type ${type}`)

if (type === 'bytes') {
return ['bytes32', ethUtil.sha3(value)]
}

if (type === 'string') {
// convert string to buffer - prevents ethUtil from interpreting strings like '0xabcd' as hex
if (typeof value === 'string') {
value = Buffer.from(value, 'utf8')
}
value = ethUtil.sha3(value)
encodedValues.push(value)
} else if (types[field.type] !== undefined) {
encodedTypes.push('bytes32')
value = ethUtil.sha3(this.encodeData(field.type, value, types))
encodedValues.push(value)
} else if (field.type.lastIndexOf(']') === field.type.length - 1) {
throw new Error('Arrays currently unimplemented in encodeData')
} else {
encodedTypes.push(field.type)
encodedValues.push(value)
return ['bytes32', ethUtil.sha3(value)]
}

if (type.lastIndexOf(']') === type.length - 1) {
const parsedType = type.slice(0, type.lastIndexOf('['))
const typeValuePairs = value.map(item =>
encodeField(name, parsedType, item))
return ['bytes32', ethUtil.sha3(ethAbi.rawEncode(
typeValuePairs.map(([type]) => type),
typeValuePairs.map(([, value]) => value),
))]
}

return [type, value]
}

for (const field of types[primaryType]) {
const [type, value] = encodeField(field.name, field.type, data[field.name])
encodedTypes.push(type)
encodedValues.push(value)
}
} else {
for (const field of types[primaryType]) {
let value = data[field.name]
if (value !== undefined) {
if (field.type === 'bytes') {
encodedTypes.push('bytes32')
value = ethUtil.sha3(value)
encodedValues.push(value)
} else if (field.type === 'string') {
encodedTypes.push('bytes32')
// convert string to buffer - prevents ethUtil from interpreting strings like '0xabcd' as hex
if (typeof value === 'string') {
value = Buffer.from(value, 'utf8')
}
value = ethUtil.sha3(value)
encodedValues.push(value)
} else if (types[field.type] !== undefined) {
encodedTypes.push('bytes32')
value = ethUtil.sha3(this.encodeData(field.type, value, types, useV4))
encodedValues.push(value)
} else if (field.type.lastIndexOf(']') === field.type.length - 1) {
throw new Error('Arrays currently unimplemented in encodeData')
} else {
encodedTypes.push(field.type)
encodedValues.push(value)
}
}
}
}
Expand Down Expand Up @@ -105,6 +148,7 @@ const TypedDataUtils = {
* @returns {Array} - Set of all types found in the type definition
*/
findTypeDependencies (primaryType, types, results = []) {
primaryType = primaryType.match(/^\w*/)[0]
if (results.includes(primaryType) || types[primaryType] === undefined) { return results }
results.push(primaryType)
for (const field of types[primaryType]) {
Expand All @@ -123,8 +167,8 @@ const TypedDataUtils = {
* @param {Object} types - Type definitions
* @returns {string} - Hash of an object
*/
hashStruct (primaryType, data, types) {
return ethUtil.sha3(this.encodeData(primaryType, data, types))
hashStruct (primaryType, data, types, useV4 = true) {
return ethUtil.sha3(this.encodeData(primaryType, data, types, useV4))
},

/**
Expand All @@ -149,6 +193,9 @@ const TypedDataUtils = {
for (const key in TYPED_MESSAGE_SCHEMA.properties) {
data[key] && (sanitizedData[key] = data[key])
}
if (sanitizedData.types) {
sanitizedData.types = Object.assign({ EIP712Domain: [] }, sanitizedData.types)
}
return sanitizedData
},

Expand All @@ -158,12 +205,12 @@ const TypedDataUtils = {
* @param {Object} typedData - Types message data to sign
* @returns {string} - sha3 hash of the resulting signed message
*/
sign (typedData) {
sign (typedData, useV4 = true) {
const sanitizedData = this.sanitizeData(typedData)
const parts = [Buffer.from('1901', 'hex')]
parts.push(this.hashStruct('EIP712Domain', sanitizedData.domain, sanitizedData.types))
parts.push(this.hashStruct('EIP712Domain', sanitizedData.domain, sanitizedData.types, useV4))
if (sanitizedData.primaryType !== 'EIP712Domain') {
parts.push(this.hashStruct(sanitizedData.primaryType, sanitizedData.message, sanitizedData.types))
parts.push(this.hashStruct(sanitizedData.primaryType, sanitizedData.message, sanitizedData.types, useV4))
}
return ethUtil.sha3(Buffer.concat(parts))
},
Expand Down Expand Up @@ -361,12 +408,25 @@ module.exports = {
},

signTypedData: function (privateKey, msgParams) {
const message = TypedDataUtils.sign(msgParams.data, false)
const sig = ethUtil.ecsign(message, privateKey)
return ethUtil.bufferToHex(this.concatSig(sig.v, sig.r, sig.s))
},

signTypedData_v4: function (privateKey, msgParams) {
const message = TypedDataUtils.sign(msgParams.data)
const sig = ethUtil.ecsign(message, privateKey)
return ethUtil.bufferToHex(this.concatSig(sig.v, sig.r, sig.s))
},

recoverTypedSignature: function (msgParams) {
const message = TypedDataUtils.sign(msgParams.data, false)
const publicKey = recoverPublicKey(message, msgParams.sig)
const sender = ethUtil.publicToAddress(publicKey)
return ethUtil.bufferToHex(sender)
},

recoverTypedSignature_v4: function (msgParams) {
const message = TypedDataUtils.sign(msgParams.data)
const publicKey = recoverPublicKey(message, msgParams.sig)
const sender = ethUtil.publicToAddress(publicKey)
Expand Down
Loading