DASL.CID (dasl v0.1.0)

View Source

DASL content identifier (CID).

A CID is a self-describing content address: it encodes a version, a codec (:raw or :drisl), and a SHA-256 digest. CIDs can be round-tripped through their canonical multibase string form, constructed by hashing arbitrary data with compute/2, and verified against their content with verify?/2.

Spec: https://dasl.ing/cid.html

Summary

Functions

Computes a CID for an arbitrary binary, defaulting to the :raw codec.

Decodes a raw CID bytestring into its constituent fields.

Encodes a DASL.CID back to its canonical string form.

Constructs a DASL.CID from a CBOR tag 42 value.

Constructs a DASL.CID from a string-encoded CID.

Parses a string-encoded CID into raw bytes.

Converts a DASL.CID to a CBOR tag 42 value.

Returns true if data hashes to the digest recorded in cid, false otherwise.

Types

codec()

@type codec() :: :raw | :drisl

t()

@type t() :: %DASL.CID{
  bytes: binary(),
  codec: codec(),
  digest: binary(),
  hash_size: pos_integer(),
  hash_type: non_neg_integer(),
  version: pos_integer()
}

Functions

compute(data, codec \\ :raw)

@spec compute(binary(), codec()) :: t()

Computes a CID for an arbitrary binary, defaulting to the :raw codec.

Examples

iex> cid = DASL.CID.compute("hello world")
iex> cid.codec
:raw
iex> cid.version
1
iex> cid.hash_size
32

iex> cid = DASL.CID.compute("hello world", :drisl)
iex> cid.codec
:drisl

decode(arg1)

@spec decode(binary()) :: {:ok, map()} | {:error, String.t()}

Decodes a raw CID bytestring into its constituent fields.

Returns {:ok, map} on success, or {:error, message} if the bytes do not conform to the CID spec (unsupported version, unknown codec, wrong hash algorithm or size, truncated input).

Examples

iex> bytes = <<1, 85, 18, 32, 185, 77, 39, 185, 147, 77, 62, 8, 165, 46, 82, 215,
...>           218, 125, 171, 250, 196, 132, 239, 227, 122, 83, 128, 238, 144, 136,
...>           247, 172, 226, 239, 205, 233>>
iex> DASL.CID.decode(bytes)
{:ok, %{version: 1, codec: :raw, hash_type: 18, hash_size: 32,
        digest: <<185, 77, 39, 185, 147, 77, 62, 8, 165, 46, 82, 215, 218, 125,
                  171, 250, 196, 132, 239, 227, 122, 83, 128, 238, 144, 136, 247,
                  172, 226, 239, 205, 233>>}}

iex> DASL.CID.decode(<<2, 85, 18, 32>> <> :binary.copy(<<0>>, 32))
{:error, "unsupported CID version: 2"}

iex> DASL.CID.decode(<<1, 0xAB, 18, 32>> <> :binary.copy(<<0>>, 32))
{:error, "unsupported codec: 0xAB"}

iex> DASL.CID.decode(<<1, 85, 0x11, 32>> <> :binary.copy(<<0>>, 32))
{:error, "unsupported hash type: 0x11"}

iex> DASL.CID.decode(<<1, 85, 18, 31>> <> :binary.copy(<<0>>, 32))
{:error, "invalid hash size: 31"}

encode(cid)

@spec encode(t()) :: String.t()

Encodes a DASL.CID back to its canonical string form.

Examples

iex> {:ok, cid} = DASL.CID.new("bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e")
iex> DASL.CID.encode(cid)
"bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e"

from_cbor(arg1)

@spec from_cbor(CBOR.Tag.t()) :: {:ok, t()} | {:error, String.t()}

Constructs a DASL.CID from a CBOR tag 42 value.

Examples

iex> {:ok, cid} = DASL.CID.new("bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e")
iex> tag = DASL.CID.to_cbor(cid)
iex> {:ok, decoded} = DASL.CID.from_cbor(tag)
iex> decoded.codec
:raw

iex> DASL.CID.from_cbor(%CBOR.Tag{tag: 1, value: "not a cid"})
{:error, "invalid CBOR CID tag"}

new(cid_string)

@spec new(String.t()) :: {:ok, t()} | {:error, String.t()}

Constructs a DASL.CID from a string-encoded CID.

Examples

iex> {:ok, cid} = DASL.CID.new("bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e")
iex> cid.version
1
iex> cid.codec
:raw
iex> cid.hash_type
18
iex> cid.hash_size
32

iex> DASL.CID.new("not-a-cid")
{:error, "CID must start with 'b'"}

parse(arg1)

@spec parse(String.t()) :: {:ok, binary()} | {:error, String.t()}

Parses a string-encoded CID into raw bytes.

Examples

iex> DASL.CID.parse("bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e")
{:ok, <<1, 85, 18, 32, 185, 77, 39, 185, 147, 77, 62, 8, 165, 46, 82, 215, 218,
        125, 171, 250, 196, 132, 239, 227, 122, 83, 128, 238, 144, 136, 247, 172,
        226, 239, 205, 233>>}

iex> DASL.CID.parse("zQmInvalidPrefix")
{:error, "CID must start with 'b'"}

iex> DASL.CID.parse("b!!!!notbase32")
{:error, "invalid base32 encoding"}

to_cbor(cid)

@spec to_cbor(t()) :: CBOR.Tag.t()

Converts a DASL.CID to a CBOR tag 42 value.

Examples

iex> {:ok, cid} = DASL.CID.new("bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e")
iex> DASL.CID.to_cbor(cid)
%CBOR.Tag{
  tag: 42,
  value: %CBOR.Tag{
    tag: :bytes,
    value: <<0, 1, 85, 18, 32, 185, 77, 39, 185, 147, 77, 62, 8, 165, 46, 82,
             215, 218, 125, 171, 250, 196, 132, 239, 227, 122, 83, 128, 238,
             144, 136, 247, 172, 226, 239, 205, 233>>
  }
}

verify?(cid, data)

@spec verify?(t(), binary()) :: boolean()

Returns true if data hashes to the digest recorded in cid, false otherwise.

Uses a constant-time comparison to avoid timing attacks.

Examples

iex> cid = DASL.CID.compute("hello world")
iex> DASL.CID.verify?(cid, "hello world")
true

iex> cid = DASL.CID.compute("hello world")
iex> DASL.CID.verify?(cid, "goodbye world")
false