UNPKG

@confluxfans/cip-23

Version:

Tiny library with utility functions that can help with signing and verifying CIP-23 based messages

266 lines (229 loc) 13.1 kB
import invalidArrayLength from './__fixtures__/invalid-array-length.json'; import invalidArrayType from './__fixtures__/invalid-array-type.json'; import invalidMissingData from './__fixtures__/invalid-missing-data.json'; import invalidMissingType from './__fixtures__/invalid-missing-type.json'; import invalidSchema from './__fixtures__/invalid-schema.json'; import invalidType from './__fixtures__/invalid-type.json'; import mailTypedData from './__fixtures__/typed-data-1.json'; import approvalTypedData from './__fixtures__/typed-data-2.json'; import arrayTypedData from './__fixtures__/typed-data-3.json'; import { asArray, encodeData, encodeType, getDependencies, getMessage, getStructHash, getTypeHash } from './cip-23'; describe('getDependencies', () => { it('returns all dependencies for the primary type', () => { expect(getDependencies(mailTypedData, 'CIP23Domain')).toStrictEqual(['CIP23Domain']); expect(getDependencies(mailTypedData, 'Person')).toStrictEqual(['Person']); expect(getDependencies(mailTypedData, 'Mail')).toStrictEqual(['Mail', 'Person']); expect(getDependencies(approvalTypedData, 'CIP23Domain')).toStrictEqual(['CIP23Domain']); expect(getDependencies(approvalTypedData, 'Transaction')).toStrictEqual(['Transaction']); expect(getDependencies(approvalTypedData, 'TransactionApproval')).toStrictEqual([ 'TransactionApproval', 'Transaction' ]); expect(getDependencies(arrayTypedData, 'CIP23Domain')).toStrictEqual(['CIP23Domain']); expect(getDependencies(arrayTypedData, 'Person')).toStrictEqual(['Person']); expect(getDependencies(arrayTypedData, 'Mail')).toStrictEqual(['Mail', 'Person']); }); it('throws for invalid JSON data', () => { // @ts-expect-error type is missing `CIP23Domain` expect(() => getDependencies(invalidSchema, 'CIP23Domain')).toThrow(); }); it('throws for invalid types', () => { expect(() => getDependencies(invalidType, 'CIP23Domain')).toThrow(); expect(() => getDependencies(invalidType, 'Person')).toThrow(); expect(() => getDependencies(invalidType, 'Mail')).toThrow(); }); }); describe('encodeType', () => { it('encodes a type to a hashable string', () => { expect(encodeType(mailTypedData, 'CIP23Domain')).toBe( 'CIP23Domain(string name,string version,uint256 chainId,address verifyingContract)' ); expect(encodeType(mailTypedData, 'Person')).toBe('Person(string name,address wallet)'); expect(encodeType(mailTypedData, 'Mail')).toBe( 'Mail(Person from,Person to,string contents)Person(string name,address wallet)' ); expect(encodeType(approvalTypedData, 'CIP23Domain')).toBe( 'CIP23Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)' ); expect(encodeType(approvalTypedData, 'Transaction')).toBe( 'Transaction(address to,uint256 amount,bytes data,uint256 nonce)' ); expect(encodeType(approvalTypedData, 'TransactionApproval')).toBe( 'TransactionApproval(address owner,Transaction transaction)Transaction(address to,uint256 amount,bytes data,uint256 nonce)' ); }); it('throws for invalid JSON data', () => { // @ts-expect-error type is missing `CIP23Domain` expect(() => encodeType(invalidSchema, 'CIP23Domain')).toThrow(); }); it('throws for invalid types', () => { expect(() => encodeType(invalidType, 'CIP23Domain')).toThrow(); expect(() => encodeType(invalidType, 'Person')).toThrow(); expect(() => encodeType(invalidType, 'Mail')).toThrow(); }); }); describe('getTypeHash', () => { it('returns a 32 byte hash for a type', () => { expect(getTypeHash(mailTypedData, 'CIP23Domain').toString('hex')).toBe( 'f84835081e4bf7951170279938141a886e3f74e3ad5baf55f0aa17d764ab3192' ); expect(getTypeHash(mailTypedData, 'Person').toString('hex')).toBe( 'b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500' ); expect(getTypeHash(mailTypedData, 'Mail').toString('hex')).toBe( 'a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2' ); expect(getTypeHash(approvalTypedData, 'CIP23Domain').toString('hex')).toBe( 'f7cec43ebb312d0122e44bda756ceb951e3659b67c937292db13c8149afba0a4' ); expect(getTypeHash(approvalTypedData, 'Transaction').toString('hex')).toBe( 'a826c254899945d99ae513c9f1275b904f19492f4438f3d8364fa98e70fbf233' ); expect(getTypeHash(approvalTypedData, 'TransactionApproval').toString('hex')).toBe( '5b360b7b2cc780b6a0687ac409805af3219ef7d9dcc865669e39a1dc7394ffc5' ); expect(getTypeHash(arrayTypedData, 'CIP23Domain').toString('hex')).toBe( 'f84835081e4bf7951170279938141a886e3f74e3ad5baf55f0aa17d764ab3192' ); expect(getTypeHash(arrayTypedData, 'Mail').toString('hex')).toBe( 'c81112a69b6596b8bc0678e67d97fbf9bed619811fc781419323ec02d1c7290d' ); expect(getTypeHash(arrayTypedData, 'Person').toString('hex')).toBe( 'b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500' ); }); it('throws for invalid JSON data', () => { // @ts-expect-error type is missing `CIP23Domain` expect(() => getTypeHash(invalidSchema, 'CIP23Domain')).toThrow(); }); }); describe('encodeData', () => { it('encodes data to an ABI encoded string', () => { expect(encodeData(mailTypedData, 'CIP23Domain', mailTypedData.domain).toString('hex')).toBe( 'f84835081e4bf7951170279938141a886e3f74e3ad5baf55f0aa17d764ab3192c70ef06638535b4881fafcac8287e210e3769ff1a8e91f1b95d6246e61e4d3c6c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cccccccccccccccccccccccccccccccccccccccc' ); expect(encodeData(mailTypedData, 'Person', mailTypedData.message.from).toString('hex')).toBe( 'b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c795008c1d2bd5348394761719da11ec67eedae9502d137e8940fee8ecd6f641ee1648000000000000000000000000cd2a3d9f938e13cd947ec05abc7fe734df8dd826' ); expect(encodeData(mailTypedData, 'Mail', mailTypedData.message).toString('hex')).toBe( 'a0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8cd54f074a4af31b4411ff6a60c9719dbd559c221c8ac3492d9d872b041d703d1b5aadf3154a261abdd9086fc627b61efca26ae5702701d05cd2305f7c52a2fc8' ); expect(encodeData(approvalTypedData, 'CIP23Domain', approvalTypedData.domain).toString('hex')).toBe( 'f7cec43ebb312d0122e44bda756ceb951e3659b67c937292db13c8149afba0a4d210ccb0bd8574cfdb6efd17ae4e6ab527687a29dcf03060d4a41b9b56d0b637c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc60000000000000000000000000000000000000000000000000000000000000001000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1dbbd6c8d75f4b446bcb44cee3ba5da8120e056d4d2f817213df8703ef065ed3' ); expect(encodeData(approvalTypedData, 'Transaction', approvalTypedData.message.transaction).toString('hex')).toBe( 'a826c254899945d99ae513c9f1275b904f19492f4438f3d8364fa98e70fbf2330000000000000000000000004bbeeb066ed09b7aed07bf39eee0460dfa2615200000000000000000000000000000000000000000000000000de0b6b3a7640000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4700000000000000000000000000000000000000000000000000000000000000001' ); expect(encodeData(approvalTypedData, 'TransactionApproval', approvalTypedData.message).toString('hex')).toBe( '5b360b7b2cc780b6a0687ac409805af3219ef7d9dcc865669e39a1dc7394ffc5000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb9e7ba42b4ace63ae7d8ee163d5e642a085b32c2553717dcb37974e83fad289d0' ); expect(encodeData(arrayTypedData, 'Mail', arrayTypedData.message).toString('hex')).toBe( 'c81112a69b6596b8bc0678e67d97fbf9bed619811fc781419323ec02d1c7290dafd2599280d009dcb3e261f4bccebec901d67c3f54b56d49bf8327359fc69cd7392bb8ab5338a9075ce8fec1b431e334007d4de1e5e83201ca35762e24428e24b7c4150525d88db452c5f08f93f4593daa458ab6280b012532183aed3a8e4a01' ); }); it('throws for invalid JSON data', () => { // @ts-expect-error type is missing `CIP23Domain` expect(() => encodeData(invalidSchema, 'CIP23Domain', invalidSchema.domain)).toThrow(); }); it('throws when a type is missing', () => { expect(() => encodeData(invalidMissingData, 'Mail', invalidMissingData.message)).toThrow(); }); it('throws when data is missing', () => { expect(() => encodeData(invalidMissingType, 'Mail', invalidMissingType.message)).toThrow(); }); it('throws if the type is not an array', () => { expect(() => encodeData(invalidArrayType, 'Mail', invalidArrayType.message)).toThrow(); }); it('throws if the array length is invalid', () => { expect(() => encodeData(invalidArrayLength, 'Mail', invalidArrayLength.message)).toThrow(); }); }); describe('getStructHash', () => { it('returns a 32 byte hash for a struct', () => { expect(getStructHash(mailTypedData, 'CIP23Domain', mailTypedData.domain).toString('hex')).toBe( '08d4df1fd1a7d9c1a27a86b3b19b3258bd6f07d9ed1b88f52705f12453a4a5a1' ); expect(getStructHash(mailTypedData, 'Person', mailTypedData.message.from).toString('hex')).toBe( 'fc71e5fa27ff56c350aa531bc129ebdf613b772b6604664f5d8dbe21b85eb0c8' ); expect(getStructHash(mailTypedData, 'Mail', mailTypedData.message).toString('hex')).toBe( 'c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e' ); expect(getStructHash(approvalTypedData, 'CIP23Domain', approvalTypedData.domain).toString('hex')).toBe( '77dac29bf5516568c33cba3cb4857224300315f419a0949f6cc44c15fce6745d' ); expect(getStructHash(approvalTypedData, 'Transaction', approvalTypedData.message.transaction).toString('hex')).toBe( '9e7ba42b4ace63ae7d8ee163d5e642a085b32c2553717dcb37974e83fad289d0' ); expect(getStructHash(approvalTypedData, 'TransactionApproval', approvalTypedData.message).toString('hex')).toBe( '309886ad75ec7c2c6a69bffa2669bad00e3b1e0a85221eff4e8926a2f8ff5077' ); }); it('throws for invalid JSON data', () => { // @ts-expect-error type is missing `CIP23Domain` expect(() => getStructHash(invalidSchema, 'CIP23Domain', invalidSchema.domain)).toThrow(); }); it('throws when a type is missing', () => { expect(() => encodeData(invalidMissingType, 'Mail', invalidSchema.message)).toThrow(); }); it('throws when data is missing', () => { expect(() => encodeData(invalidMissingType, 'Mail', invalidSchema.message)).toThrow(); }); }); describe('getMessage', () => { it('returns the full encoded and hashed message to sign', () => { expect(getMessage(mailTypedData).toString('hex')).toBe( '190108d4df1fd1a7d9c1a27a86b3b19b3258bd6f07d9ed1b88f52705f12453a4a5a1c52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e' ); expect(getMessage(approvalTypedData).toString('hex')).toBe( '190177dac29bf5516568c33cba3cb4857224300315f419a0949f6cc44c15fce6745d309886ad75ec7c2c6a69bffa2669bad00e3b1e0a85221eff4e8926a2f8ff5077' ); expect(getMessage(arrayTypedData).toString('hex')).toBe( '190108d4df1fd1a7d9c1a27a86b3b19b3258bd6f07d9ed1b88f52705f12453a4a5a16757567025d2ba15d5ebb228ea677055b8b601007e60e9463f6ed7c68f918189' ); }); it('hashes the message with Keccak-256', () => { expect(getMessage(mailTypedData, true).toString('hex')).toBe( 'f930c72ca47e411d8671f3bee80e1d7594cd17a04355b15db5f11c2aba0a54e9' ); expect(getMessage(approvalTypedData, true).toString('hex')).toBe( '624cb33787443b5319e6b56c495dfa70d6ff2e23a7ddb3f5f1d132bf456543de' ); expect(getMessage(arrayTypedData, true).toString('hex')).toBe( '38ac2adfb33e2cb9358afd39cdea9aa0543df5e34634d4196abd50099777aee6' ); }); it('throws for invalid JSON data', () => { // @ts-expect-error type is missing `CIP23Domain` expect(() => getMessage(invalidSchema)).toThrow(); }); it('throws when a type is missing', () => { expect(() => getMessage(invalidMissingType)).toThrow(); }); it('throws when data is missing', () => { expect(() => getMessage(invalidMissingData)).toThrow(); }); }); describe('asArray', () => { it('returns the typed data as array', () => { expect(asArray(mailTypedData)).toStrictEqual([ ['Cow', '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'], ['Bob', '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB'], 'Hello, Bob!' ]); expect(asArray(approvalTypedData)).toStrictEqual([ '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', ['0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520', '1000000000000000000', '', '1'] ]); }); it('throws for invalid JSON data', () => { // @ts-expect-error type is missing `CIP23Domain` expect(() => asArray(invalidSchema)).toThrow(); }); it('throws when a type is missing', () => { expect(() => asArray(invalidMissingType)).toThrow(); }); it('throws when data is missing', () => { expect(() => asArray(invalidMissingData)).toThrow(); }); });