import { NULL_ADDRESS , trimLeading0x } from '@celo/base/lib/address' ; import { BigNumber } from 'bignumber.js' ; import { keccak } from 'ethereumjs-util' ; import * as t from 'io-ts' ; import coder from 'web3-eth-abi' ; export interface EIP712Parameter { name : string ; type : string ; } export interface EIP712Types { [ key ] : EIP712Parameter [ ] ; } export interface EIP712TypesWithPrimary { types : EIP712Types ; primaryType : string ; } export type EIP712ObjectValue = string | number | BigNumber | boolean | Buffer | EIP712Object | EIP712ObjectValue [ ] ; export interface EIP712Object { [ key ] : EIP712ObjectValue ; } export interface EIP712TypedData { types : EIP712Types & { EIP712Domain : EIP712Parameter [ ] ; } ; domain : EIP712Object ; message : EIP712Object ; primaryType : string ; } /** Array of all EIP-712 atomic type names. */ export const EIP712_ATOMIC_TYPES = [ 'bytes1' , 'bytes32' , 'uint8' , 'uint256' , 'int8' , 'int256' , 'bool' , 'address' , ] ; export const EIP712_DYNAMIC_TYPES = [ 'bytes' , 'string' ] ; export const EIP712_BUILTIN_TYPES = EIP712_ATOMIC_TYPES . concat ( EIP712_DYNAMIC_TYPES ) ; const EIP712_ARRAY_REGEXP = /^(?<memberType>[\w<>\[\]_\-]+)(\[(?<fixedLength>\d+)?\])$/ ; const EIP712_INT_REGEXP = /^u?int\d*$/ ; remarks export type EIP712Optional < T extends EIP712ObjectValue > = { defined : boolean ; value : T ; } ; param typeName export const eip712OptionalType = ( typeName ) : EIP712Types => ( { [ `Optional<${ typeName }>` ] : [ { name : 'defined' , type : 'bool' } , { name : 'value' , type : typeName } , ] } ) ; param schema export const eip712OptionalSchema = < S extends t . Mixed > ( schema ) => t . type ( { defined : t . boolean , value : schema } ) ; /** Utility to construct an defined EIP712Optional value with inferred type. */ export const defined = < T extends EIP712ObjectValue > ( value ) : EIP712Optional < T > => ( { defined : true , value } ) ; /** Undefined EIP712Optional type with value type boolean. */ export const noBool = { defined : false , value : false } ; /** Undefined EIP712Optional type with value type number. */ export const noNumber = { defined : false , value : 0 } ; /** Undefined EIP712Optional type with value type string. */ export const noString = { defined : false , value : '' } ; param typedData return export function generateTypedDataHash ( typedData ) { return keccak ( Buffer . concat ( [ Buffer . from ( '1901' , 'hex' ) , structHash ( 'EIP712Domain' , typedData . domain , typedData . types ) , structHash ( typedData . primaryType , typedData . message , typedData . types ) , ] ) ) as Buffer ; } /**
 * Given the primary type, and dictionary of types, this function assembles a sorted list
 * representing the transitive dependency closure of the primary type. (Inclusive of the primary
 * type itself.)
 */ function findDependencies ( primaryType , types , found = [ ] ) { if ( found . includes ( primaryType ) || EIP712_BUILTIN_TYPES . includes ( primaryType ) ) { return [ ] ; } if ( EIP712_ARRAY_REGEXP . test ( primaryType ) ) { const match = EIP712_ARRAY_REGEXP . exec ( primaryType ) ; const memberType = match ?. groups ?. memberType ! ; return findDependencies ( memberType , types , found ) ; } if ( types [ primaryType ] === undefined ) { throw new Error ( `Unrecognized type ${ primaryType } is not included in the EIP-712 type list` ) ; } const dependencies = [ primaryType ] ; for ( const field of types [ primaryType ] ) { dependencies . push ( ... findDependencies ( field . type , types , found . concat ( dependencies ) ) ) ; } return dependencies ; } /**
 * Creates a string encoding of the primary type, including all dependencies.
 * E.g. "Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)"
 */ export function encodeType ( primaryType , types ) { let deps = findDependencies ( primaryType , types ) ; deps = deps . filter ( ( d ) => d !== primaryType ) ; deps = [ primaryType ] . concat ( deps . sort ( ) ) ; let result = '' ; for ( const dep of deps ) { result += `${ dep }(${ types [ dep ] . map ( ( { name , type } ) => `${ type } ${ name }` ) . join ( ',' ) })` ; } return result ; } export function typeHash ( primaryType , types ) { return keccak ( encodeType ( primaryType , types ) ) as Buffer ; } /** Encodes a single EIP-712 value to a 32-byte buffer */ function encodeValue ( valueType , value , types ) { if ( EIP712_ATOMIC_TYPES . includes ( valueType ) ) { const hexEncoded = coder . encodeParameter ( valueType , normalizeValue ( valueType , value ) ) ; return Buffer . from ( trimLeading0x ( hexEncoded ) , 'hex' ) ; } if ( valueType === 'string' ) { return keccak ( Buffer . from ( value as string , 'utf8' ) ) as Buffer ; } if ( valueType === 'bytes' ) { return keccak ( value as string ) as Buffer ; } if ( types [ valueType ] !== undefined ) { return structHash ( valueType , value as EIP712Object , types ) ; } if ( EIP712_ARRAY_REGEXP . test ( valueType ) ) { const match = EIP712_ARRAY_REGEXP . exec ( valueType ) ; const memberType = match ?. groups ?. memberType ! ; return keccak ( Buffer . concat ( ( value as EIP712ObjectValue [ ] ) . map ( ( member ) => encodeValue ( memberType , member , types ) ) ) ) as Buffer ; } throw new Error ( `Unrecognized or unsupported type in EIP-712 encoding: ${ valueType }` ) ; } function normalizeValue ( type , value ) { const normalizedValue = EIP712_INT_REGEXP . test ( type ) && BigNumber . isBigNumber ( value ) ? value . toString ( ) : value ; return normalizedValue ; } /**
 * Constructs the struct encoding of the data as the primary type.
 */ export function encodeData ( primaryType , data , types ) { const fields = types [ primaryType ] ; if ( fields === undefined ) { throw new Error ( `Unrecognized primary type in EIP-712 encoding: ${ primaryType }` ) ; } return Buffer . concat ( fields . map ( ( field ) => encodeValue ( field . type , data [ field . name ] , types ) ) ) ; } export function structHash ( primaryType , data , types ) { return keccak ( Buffer . concat ( [ typeHash ( primaryType , types ) , encodeData ( primaryType , data , types ) ] ) ) as Buffer ; } remarks export function zeroValue ( primaryType , types = { } ) { if ( [ 'bytes' , 'bytes1' , 'bytes32' ] . includes ( primaryType ) ) { return Buffer . alloc ( 0 ) ; } if ( [ 'uint8' , 'uint256' , 'int8' , 'int256' ] . includes ( primaryType ) ) { return 0 ; } if ( primaryType === 'bool' ) { return false ; } if ( primaryType === 'address' ) { return NULL_ADDRESS ; } if ( primaryType === 'string' ) { return '' ; } if ( EIP712_ARRAY_REGEXP . test ( primaryType ) ) { const match = EIP712_ARRAY_REGEXP . exec ( primaryType ) ; const memberType = match ?. groups ?. memberType ! ; const fixedLengthStr = match ?. groups ?. fixedLength ; const fixedLength = fixedLengthStr === undefined ? 0 : parseInt ( fixedLengthStr , 10 ) ; return [ ... Array ( fixedLength ) . keys ( ) ] . map ( ( ) => zeroValue ( memberType , types ) ) ; } const fields = types [ primaryType ] ; if ( fields === undefined ) { throw new Error ( `Unrecognized primary type for EIP-712 zero value: ${ primaryType }` ) ; } return fields . reduce ( ( obj , field ) => ( { ... obj , [ field . name ] : zeroValue ( field . type , types ) } ) , { } ) ; }