Cartouche.Typed (Cartouche v0.9.1)

Copy Markdown View Source

Module to build EIP-712 typed data, which can then be signed or recovered from.

Summary

Functions

Deserializes a Typed value from JSON or a map into a struct.

Builds a domain struct for a given type, per the EIP-712 spec.

Encodes a given typed value such that it can be signed or recovered.

Encodes the struct type per EIP-712. The primary type is encoded first as Name(type1 field1,type2 field2,...). Referenced struct types are then collected transitively, sorted by name, and appended, e.g.

Hashes a struct value, per the EIP-712 spec.

Serializes a Typed value, such that it can be passed to JSON or JavaScript.

Types

t()

@type t() :: %Cartouche.Typed{
  domain: Cartouche.Typed.Domain.t(),
  types: type_map(),
  value: value_map()
}

type_map()

@type type_map() :: %{required(String.t()) => Cartouche.Typed.Type.t()}

value_map()

@type value_map() :: %{required(String.t()) => term()}

Functions

deserialize(map)

@spec deserialize(%{}) :: t()

Deserializes a Typed value from JSON or a map into a struct.

Examples

iex> %{
...>   "domain" => %{
...>     "name" => "Ether Mail",
...>     "version" => "1",
...>     "chainId" => 1,
...>     "verifyingContract" => "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
...>   },
...>   "types" => %{
...>     "Person" => [
...>       %{
...>         "name" => "name",
...>         "type" => "string"
...>       },
...>       %{
...>         "name" => "wallet",
...>         "type" => "address"
...>       },
...>     ],
...>     "Mail" => [
...>       %{
...>         "name" => "from",
...>         "type" => "Person"
...>       },
...>       %{
...>         "name" => "to",
...>         "type" => "Person"
...>       },
...>       %{
...>         "name" => "contents",
...>         "type" => "string"
...>       },
...>     ]
...>   },
...>   "value" => %{
...>     "from" => %{ "name" => "Cow", "wallet" => "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" },
...>     "to" => %{ "name" => "Bob", "wallet" => "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" },
...>     "contents" => "Hello, Bob!"
...>   }
...> }
...> |> Cartouche.Typed.deserialize()
%Cartouche.Typed{
  domain: %Cartouche.Typed.Domain{
    chain_id: 1,
    name: "Ether Mail",
    verifying_contract: ~h[CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC],
    version: "1"
  },
  types: %{
    "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
    "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
  },
  value: %{
    "contents" => "Hello, Bob!",
    "from" => %{
      "name" => "Cow",
      "wallet" => ~h[CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826]
    },
    "to" => %{
      "name" => "Bob",
      "wallet" => ~h[BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB]
    }
  }
}

domain_seperator(typed)

@spec domain_seperator(t()) :: binary()

Builds a domain struct for a given type, per the EIP-712 spec.

Examples

iex> %Cartouche.Typed{
...>   domain: %Cartouche.Typed.Domain{
...>     chain_id: 1,
...>     name: "Ether Mail",
...>     verifying_contract: ~h[0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC],
...>     version: "1"
...>   },
...>   types: %{
...>     "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>     "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...>   },
...>   value: %{
...>     "contents" => "Hello, Bob!",
...>     "from" => %{
...>       "name" => "Cow",
...>       "wallet" => ~h[0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826]
...>     },
...>     "to" => %{
...>       "name" => "Bob",
...>       "wallet" => ~h[0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]
...>     }
...>   }
...> }
...> |> Cartouche.Typed.domain_seperator()
...> |> to_hex()
"0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"

encode(typed)

@spec encode(t()) :: binary()

Encodes a given typed value such that it can be signed or recovered.

Examples

iex> %Cartouche.Typed{
...>   domain: %Cartouche.Typed.Domain{
...>     chain_id: 1,
...>     name: "Ether Mail",
...>     verifying_contract: ~h[0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC],
...>     version: "1"
...>   },
...>   types: %{
...>     "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>     "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...>   },
...>   value: %{
...>     "contents" => "Hello, Bob!",
...>     "from" => %{
...>       "name" => "Cow",
...>       "wallet" => ~h[0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826]
...>     },
...>     "to" => %{
...>       "name" => "Bob",
...>       "wallet" => ~h[0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]
...>     }
...>   }
...> }
...> |> Cartouche.Typed.encode()
...> |> to_hex()
"0x1901f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090fc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"

iex> %Cartouche.Typed{
...>   domain: %Cartouche.Typed.Domain{
...>     chain_id: 1,
...>     name: "Complex Array",
...>     verifying_contract: ~h[0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC],
...>     version: "1"
...>   },
...>   types: %{
...>     "Array" => %Cartouche.Typed.Type{fields: [{"a", {:uint, 256}}, {"b", {:uint, 256}}, {"c", :string}, {"d", {:array, :bytes}}]}
...>   },
...>   value: %{
...>     "a" => 55,
...>     "b" => 66,
...>     "c" => "Hello",
...>     "d" => [<<0x11, 0x22>>, <<0x33, 0x44>>]
...>   }
...> }
...> |> Cartouche.Typed.encode()
...> |> to_hex()
"0x190103bd1627b4c5f7540c63d7ee347524dcef247eed29c833dd3b1455b8dec4009fcc95538bfc3f979ca59d9ef7de5ed402a4e403857b3de87d1fc8ed4a2a7cddd9"

iex> %Cartouche.Typed{
...>   domain: %Cartouche.Typed.Domain{
...>     name: "Complex Array",
...>     version: "1"
...>   },
...>   types: %{
...>     "Array" => %Cartouche.Typed.Type{fields: [{"a", {:uint, 256}}, {"b", {:uint, 256}}, {"c", :string}, {"d", {:array, :bytes}}]}
...>   },
...>   value: %{
...>     "a" => 55,
...>     "b" => 66,
...>     "c" => "Hello",
...>     "d" => [<<0x11, 0x22>>, <<0x33, 0x44>>]
...>   }
...> }
...> |> Cartouche.Typed.encode()
...> |> to_hex()
"0x1901f4806c1a9dae718712eca4906bfca239a3a4a6dea2e9b9a1284fee5ff4df4b1ccc95538bfc3f979ca59d9ef7de5ed402a4e403857b3de87d1fc8ed4a2a7cddd9"

iex> %Cartouche.Typed{
...>   domain: %Cartouche.Typed.Domain{
...>     name: "Complex Array",
...>     version: "1"
...>   },
...>   types: %{
...>     "Array" => %Cartouche.Typed.Type{fields: [{"a", {:uint, 256}}, {"b", {:uint, 256}}, {"c", :string}, {"d", :bool}]}
...>   },
...>   value: %{
...>     "a" => 55,
...>     "b" => 66,
...>     "c" => "Hello",
...>     "d" => true
...>   }
...> }
...> |> Cartouche.Typed.encode()
...> |> to_hex()
"0x1901f4806c1a9dae718712eca4906bfca239a3a4a6dea2e9b9a1284fee5ff4df4b1c8c56315a01fe3937526fe8c2b472b7e9e1c21728f6c14d5ffb0e0c156f74aca0"

encode_type(name, types)

@spec encode_type(String.t(), type_map()) :: String.t()

Encodes the struct type per EIP-712. The primary type is encoded first as Name(type1 field1,type2 field2,...). Referenced struct types are then collected transitively, sorted by name, and appended, e.g.

Mail(Person from,Person to,string contents)Person(string name,address wallet).

Examples

iex> Cartouche.Typed.encode_type("Mail", %{
...>   "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>   "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> })
"Mail(Person from,Person to,string contents)Person(string name,address wallet)"

hash_struct(name, value, types)

@spec hash_struct(String.t(), value_map(), type_map()) :: binary()

Hashes a struct value, per the EIP-712 spec.

Examples

iex> types = %{
...>   "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>   "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...> }
...> value = %{
...>   "contents" => "Hello, Bob!",
...>   "from" => %{
...>     "name" => "Cow",
...>     "wallet" => ~h[0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826]
...>   },
...>   "to" => %{
...>     "name" => "Bob",
...>     "wallet" => ~h[0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]
...>   }
...> }
iex> to_hex(Cartouche.Typed.hash_struct("Mail", value, types))
"0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e"

serialize(typed)

@spec serialize(t()) :: %{}

Serializes a Typed value, such that it can be passed to JSON or JavaScript.

Examples

iex> %Cartouche.Typed{
...>   domain: %Cartouche.Typed.Domain{
...>     chain_id: 1,
...>     name: "Ether Mail",
...>     verifying_contract: ~h[CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC],
...>     version: "1"
...>   },
...>   types: %{
...>     "Mail" => %Cartouche.Typed.Type{fields: [{"from", "Person"}, {"to", "Person"}, {"contents", :string}]},
...>     "Person" => %Cartouche.Typed.Type{fields: [{"name", :string}, {"wallet", :address}]}
...>   },
...>   value: %{
...>     "contents" => "Hello, Bob!",
...>     "from" => %{
...>       "name" => "Cow",
...>       "wallet" => ~h[CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826]
...>     },
...>     "to" => %{
...>       "name" => "Bob",
...>       "wallet" => ~h[BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB]
...>     }
...>   }
...> }
...> |> Cartouche.Typed.serialize()
%{
  "domain" => %{
    "name" => "Ether Mail",
    "version" => "1",
    "chainId" => 1,
    "verifyingContract" => "0xcccccccccccccccccccccccccccccccccccccccc"
  },
  "types" => %{
    "Person" => [
      %{
        "name" => "name",
        "type" => "string"
      },
      %{
        "name" => "wallet",
        "type" => "address"
      },
    ],
    "Mail" => [
      %{
        "name" => "from",
        "type" => "Person"
      },
      %{
        "name" => "to",
        "type" => "Person"
      },
      %{
        "name" => "contents",
        "type" => "string"
      },
    ]
  },
  "value" => %{
    "from" => %{ "name" => "Cow", "wallet" => "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826" },
    "to" => %{ "name" => "Bob", "wallet" => "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" },
    "contents" => "Hello, Bob!"
  }
}