Atex.Repo.Commit
(atex v0.9.1)
View Source
The signed commit object at the top of an AT Protocol repository.
A commit binds together:
- The account DID that owns the repository.
- A CID link (
data) to the root of the MST that holds all records. - A monotonically-increasing revision (
rev) in TID string format, used as a logical clock. - A
prevlink to the previous commit (virtually alwaysnilin v3 repos, but the field must be present in the CBOR object). - A cryptographic
sigover the DRISL CBOR encoding of the unsigned commit.
Signing a commit
The signing convention follows the AT Protocol repository spec:
- Build an unsigned commit (all fields except
sig). - Encode it with
encode_unsigned/1to get the DRISL CBOR bytes. - SHA-256 hash the bytes, then ECDSA-sign the hash with the account's signing key.
- Store the raw (DER-encoded) signature bytes in
sig.
sign/2 performs steps 2–4 in one call. Verification with verify/2
reverses the process using a public key.
CID computation
The CID for a commit is computed from the DRISL CBOR encoding of the signed
commit object (with sig present), using the :drisl codec.
Wire format
Map keys follow the AT Protocol specification field names:
"did"- account DID string"version"- integer3"data"- CID link to MST root"rev"- TID string"prev"- CID link ornil"sig"- raw ECDSA signature bytes (absent from the unsigned map)
ATProto spec: https://atproto.com/specs/repository#commit-objects
Summary
Functions
Computes the CID of a signed commit.
Decodes a DRISL CBOR binary into a %Atex.Repo.Commit{}.
Serializes a signed commit (including sig) as DRISL CBOR.
Serializes the commit without the sig field as DRISL CBOR.
Builds an unsigned commit struct from the given fields.
Signs an unsigned commit with the given private key.
Verifies the signature of a signed commit against the given public key.
Types
@type t() :: %Atex.Repo.Commit{ data: DASL.CID.t(), did: String.t(), prev: DASL.CID.t() | nil, rev: String.t(), sig: binary() | nil, version: pos_integer() }
A v3 AT Protocol repository commit.
Functions
@spec cid(t()) :: {:ok, DASL.CID.t()} | {:error, :unsigned | atom()}
Computes the CID of a signed commit.
The CID is derived from the DRISL CBOR encoding of the signed commit
object, using the :drisl codec (blessed CID format).
Returns {:error, :unsigned} if sig is nil.
Examples
iex> data_cid = DASL.CID.compute("data", :drisl)
iex> commit = Atex.Repo.Commit.new(did: "did:plc:e", data: data_cid, rev: "3jzfcijpj2z2a")
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, signed} = Atex.Repo.Commit.sign(commit, jwk)
iex> {:ok, cid} = Atex.Repo.Commit.cid(signed)
iex> cid.codec
:drisl
Decodes a DRISL CBOR binary into a %Atex.Repo.Commit{}.
Accepts both signed (with "sig") and unsigned (without "sig") payloads.
Examples
iex> data_cid = DASL.CID.compute("data", :drisl)
iex> commit = Atex.Repo.Commit.new(did: "did:plc:e", data: data_cid, rev: "3jzfcijpj2z2a")
iex> {:ok, bin} = Atex.Repo.Commit.encode_unsigned(commit)
iex> {:ok, decoded, ""} = Atex.Repo.Commit.decode(bin)
iex> decoded.did
"did:plc:e"
Serializes a signed commit (including sig) as DRISL CBOR.
Returns {:error, :unsigned} if sig is nil.
Examples
iex> data_cid = DASL.CID.compute("data", :drisl)
iex> commit = Atex.Repo.Commit.new(did: "did:plc:e", data: data_cid, rev: "3jzfcijpj2z2a")
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, signed} = Atex.Repo.Commit.sign(commit, jwk)
iex> {:ok, bin} = Atex.Repo.Commit.encode(signed)
iex> is_binary(bin)
true
Serializes the commit without the sig field as DRISL CBOR.
This is the payload that is hashed and signed. The sig field is omitted
entirely from the map, as required by the spec.
Examples
iex> data_cid = DASL.CID.compute("data", :drisl)
iex> commit = Atex.Repo.Commit.new(did: "did:plc:e", data: data_cid, rev: "3jzfcijpj2z2a")
iex> {:ok, bin} = Atex.Repo.Commit.encode_unsigned(commit)
iex> is_binary(bin)
true
Builds an unsigned commit struct from the given fields.
sig is set to nil.
Options
:did(required) - the account DID string:data(required) -DASL.CIDpointing to the MST root:rev(required) - TID string used as the logical clock:prev-DASL.CIDpointing to the previous commit, ornil(default)
Examples
iex> data_cid = DASL.CID.compute("data", :drisl)
iex> commit = Atex.Repo.Commit.new(
...> did: "did:plc:example",
...> data: data_cid,
...> rev: "3jzfcijpj2z2a"
...> )
iex> commit.version
3
iex> commit.sig
nil
@spec sign(t(), JOSE.JWK.t()) :: {:ok, t()} | {:error, :already_signed | atom()}
Signs an unsigned commit with the given private key.
Encodes the unsigned commit as DRISL CBOR and signs the bytes using
Atex.Crypto.sign/2 (SHA-256 ECDSA, low-S normalized DER output).
Returns {:error, :already_signed} if sig is already present.
Examples
iex> data_cid = DASL.CID.compute("data", :drisl)
iex> commit = Atex.Repo.Commit.new(did: "did:plc:e", data: data_cid, rev: "3jzfcijpj2z2a")
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, signed} = Atex.Repo.Commit.sign(commit, jwk)
iex> is_binary(signed.sig)
true
@spec verify(t(), JOSE.JWK.t()) :: :ok | {:error, :unsigned | atom()}
Verifies the signature of a signed commit against the given public key.
Returns :ok or {:error, reason}.
Examples
iex> data_cid = DASL.CID.compute("data", :drisl)
iex> commit = Atex.Repo.Commit.new(did: "did:plc:e", data: data_cid, rev: "3jzfcijpj2z2a")
iex> jwk = JOSE.JWK.generate_key({:ec, "P-256"})
iex> {:ok, signed} = Atex.Repo.Commit.sign(commit, jwk)
iex> Atex.Repo.Commit.verify(signed, JOSE.JWK.to_public(jwk))
:ok