Skip to content

Commit

Permalink
Uhh Git hell (#27)
Browse files Browse the repository at this point in the history
* remove .so file, upgrade rustler

* begin DidPlc

* started DidPlc classes (don't look please)

* closer to working did generation

* doctest for cbor encoding

* okay fine

* use to_bytes

* decode16

* make mix task stop crashing

* docstrings

* working cbor  (#25)

* DIdPlc stuff (#21)

* remove .so file, upgrade rustler

* begin DidPlc

* started DidPlc classes (don't look please)

* closer to working did generation

* doctest for cbor encoding

* okay fine

* use to_bytes

* decode16

* add round trip cbor test

* fix map key syntax

* trying libipld for encoding dag-cbor

* missing cid tag

maybe?

* what if we construct a tagged IPLD node with the CID tag and insert it into the result map

* manually tag it? idk

* fortune favors the bold

* mission failed, we'll get 'em next time

* update README to reflect new goals :) (#23)

* fix example case in dagcbor docs (#24)

---------

Co-authored-by: flicknow <[email protected]>
Co-authored-by: nova <[email protected]>
Co-authored-by: Samuel Newman <[email protected]>
Co-authored-by: mark <[email protected]>

* cbor lists

* little fixes

* remove extra sha256

* phew

---------

Co-authored-by: flicknow <[email protected]>
Co-authored-by: nova <[email protected]>
Co-authored-by: Samuel Newman <[email protected]>
Co-authored-by: mark <[email protected]>
  • Loading branch information
5 people authored Feb 23, 2024
1 parent d2050cd commit ed5f982
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 27 deletions.
21 changes: 17 additions & 4 deletions lib/hexpds/dagcbor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,38 @@ defmodule Hexpds.DagCBOR do
def decode_dag_cbor(_cbor), do: :erlang.nif_error(:nif_not_loaded)
end

@spec encode_json(binary()) :: {:error, binary()} | {:ok, binary()}
@spec encode(binary() | map()) :: {:error, binary()} | {:ok, binary()}
@doc """
Encodes a JSON string into a CBOR binary.
Encodes a JSON string or a map into a CBOR binary.
Examples:
iex> Hexpds.DagCBOR.encode_json(Jason.encode!(%{apple: "banana", cranberry: "dragonfruit"}))
iex> Hexpds.DagCBOR.encode(%{apple: "banana", cranberry: "dragonfruit"})
...> |> elem(1)
...> |> Base.encode16()
"A2656170706C656662616E616E61696372616E62657272796B647261676F6E6672756974"
"""
def encode_json(json) do
def encode("" <> json) do
with {:ok, cbor} <- Internal.encode_dag_cbor(json) do
{:ok, to_string(cbor)}
end
end
def encode(%{} = json) do
with {:ok, json} <- Jason.encode(json), do: encode(json)
end

def encode([_ | _] = l) do
with {:ok, json} <- Jason.encode(l), do: encode(json)
end

@spec decode_json(binary()) :: {:error, binary()} | {:ok, String.t()}
def decode_json(cbor) do
Internal.decode_dag_cbor(cbor)
end

@spec decode(binary()) :: {:error, binary() | Jason.DecodeError.t()} | {:ok, any()}
def decode (cbor) do
with {:ok, json} <- decode_json(cbor), do: Jason.decode(json)
end
end
16 changes: 8 additions & 8 deletions lib/hexpds/didgenerator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ defmodule Hexpds.DidGenerator do
require Logger
alias Hexpds.K256, as: K256

@spec genesis_to_did(map()) :: <<_::64, _::_*8>>
@spec genesis_to_did(map()) :: String.t()
def genesis_to_did(%{"type" => "plc_operation", "prev" => nil} = signed_genesis) do
"did:plc:" <>
with {:ok, signed_genesis_json} <-
signed_genesis
|> Jason.encode(),
{:ok, signed_genesis_cbor} <-
signed_genesis_json
|> Hexpds.DagCBOR.encode_json() do
:crypto.hash(:sha256, signed_genesis_cbor)
|> Base.encode32(case: :lower)
|> String.slice(0..23)
|> String.downcase()
end
|> Hexpds.DagCBOR.encode(),
do:
:crypto.hash(:sha256, signed_genesis_cbor)
|> Base.encode32(case: :lower)
|> String.slice(0..23)
|> String.downcase()
end

@spec publish_to_plc(map(), <<_::64, _::_*8>>) ::
@spec publish_to_plc(map(), String.t()) ::
{:error,
%{
:__exception__ => true,
Expand Down
6 changes: 4 additions & 2 deletions lib/hexpds/didplc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ defmodule Hexpds.DidPlc do
})
end

@spec sign(Hexpds.DidPlc.Operation.t(), Hexpds.K256.PrivateKey.t()) ::
@spec sign(t(), Hexpds.K256.PrivateKey.t()) ::
{:ok, binary()} | {:error, String.t()}
def sign(%__MODULE__{} = operation, %Hexpds.K256.PrivateKey{} = privkey) do
with {:ok, cbor} <-
operation
|> to_json()
|> Hexpds.DagCBOR.encode_json(),
|> Hexpds.DagCBOR.encode(),
do:
{:ok,
privkey
Expand All @@ -80,6 +80,8 @@ defmodule Hexpds.DidPlc do
|> String.replace("=", "")}
end

@spec add_sig(t(), Hexpds.K256.PrivateKey.t()) ::
{:error, binary()} | map()
def add_sig(%__MODULE__{} = operation, %Hexpds.K256.PrivateKey{} = privkey) do
with {:ok, sig} <- sign(operation, privkey) do
operation
Expand Down
31 changes: 25 additions & 6 deletions lib/hexpds/k256.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,38 @@ defmodule Hexpds.K256 do
"""
defmodule PrivateKey do
defstruct [:privkey]

@typedoc """
A Secp256k1 private key. Contains the raw bytes of the key, and wraps the `k256` crate's `k256::SecretKey` type.
Should always be 32 bytes (256 bits) long. All operations on `Hexpds.K256.PrivateKey` are type-safe.
"""
@type t :: %__MODULE__{privkey: <<_::256>>}

defguard is_valid_key(privkey) when is_binary(privkey) and byte_size(privkey) == 32

@spec create() :: t()
@doc """
Generates a new Secp256k1 private key.
"""
def create(), do: %__MODULE__{privkey: :crypto.strong_rand_bytes(32)}

@spec from_binary(binary()) :: t()
def from_binary(privkey), do: %__MODULE__{privkey: privkey}

@doc """
Wraps a Secp256k1 private key from its raw bytes.
"""
def from_binary(privkey) when is_valid_key(privkey), do: %__MODULE__{privkey: privkey}

@spec from_hex(String.t()) :: t()
def from_hex(hex), do: from_binary(Base.decode16!(hex, case: :lower))

@spec to_hex(t()) :: String.t()
def to_hex(%__MODULE__{} = privkey), do: Base.encode16(privkey.privkey, case: :lower)

@doc """
Converts a Secp256k1 private key to a hex-encoded string.
"""
def to_hex(%__MODULE__{privkey: privkey}) when is_valid_key(privkey),
do: Base.encode16(privkey, case: :lower)

@spec to_pubkey(t()) :: Hexpds.K256.PublicKey.t()
def to_pubkey(%__MODULE__{} = privkey) when is_valid_key(privkey.privkey),
Expand All @@ -31,10 +48,12 @@ defmodule Hexpds.K256 do
@doc """
Signs a binary message with a Secp256k1 private key. Returns a binary signature.
"""
def sign(%__MODULE__{privkey: privkey}, message) when is_binary(message) do
with {:ok, sig_hex} <- Hexpds.K256.Internal.sign_message(privkey, message),
{:ok, sig} <- Base.decode16(sig_hex, case: :lower),
do: sig

def sign(%__MODULE__{privkey: privkey}, message)
when is_binary(message) and is_valid_key(privkey) do
with {:ok, sig} <- Hexpds.K256.Internal.sign_message(privkey, message),
{:ok, sig_bytes} <- Base.decode16(sig, case: :lower),
do: sig_bytes
end

@spec sign!(t(), binary()) :: binary()
Expand Down
35 changes: 31 additions & 4 deletions lib/hexpds/tid.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
defmodule Hexpds.Tid do
import Bitwise

@type t :: %__MODULE__{timestamp: non_neg_integer(), clock_id: non_neg_integer()}
defstruct [:timestamp, :clock_id]

@typedoc """
A TID is a 13-character string.
TID is short for "timestamp identifier," and the name is derived from the creation time of the record.
The characteristics of a TID are:
- 64-bit integer
- big-endian byte ordering
- encoded as base32-sortable. That is, encoded with characters 234567abcdefghijklmnopqrstuvwxyz, with no padding, yielding 13 ASCII characters.
- hyphens should not be included in a TID (unlike in previous iterations of the scheme)
The layout of the 64-bit integer is:
- The top bit is always 0
- The next 53 bits represent microseconds since the UNIX epoch. 53 bits is chosen as the maximum safe integer precision in a 64-bit floating point number, as used by Javascript.
- The final 10 bits are a random "clock identifier."
This struct holds the timestamp in microseconds since the UNIX epoch, and the clock_id, which is a random number in the range 0..1023.
"""
@type t :: %__MODULE__{timestamp: unix_microseconds(), clock_id: non_neg_integer()}

@typedoc """
A number of microseconds since the UNIX epoch, as a 64-bit non-negative integer
"""
@type unix_microseconds :: non_neg_integer()

@b32_charset "234567abcdefghijklmnopqrstuvwxyz"

@spec from_string(String.t()) :: t() | {:error, String.t()}
Expand All @@ -21,13 +48,13 @@ defmodule Hexpds.Tid do
{timestamp_acc, clock_id_acc <<< 5 ||| (pos &&& 0x1F)}

_ ->
raise "Invalid TID"
throw("Invalid TID")
end
end)

%__MODULE__{timestamp: timestamp, clock_id: clock_id}
rescue
_ -> {:error, "Invalid TID"}
catch
:throw, e -> {:error, e}
end
end

Expand Down
2 changes: 2 additions & 0 deletions lib/mix/tasks/didgen.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ defmodule Mix.Tasks.DidPlc.Generate do
use Mix.Task
alias Hexpds.DidGenerator

HTTPoison.start()

@shortdoc "Generate a DID:PLC: and publish it to the PLC server set in config/config.exs - pass in a handle"
@impl Mix.Task
def run([handle | _]) do
Expand Down
6 changes: 3 additions & 3 deletions test/hexpds_dagcbor_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ defmodule HexpdsDagcborTest do

test "cbor roundtrip" do
for input <- test_cases() do
{:ok, cbor_encoded} = Hexpds.DagCBOR.encode_json(Jason.encode!(input))
{:ok, json_encoded} = Hexpds.DagCBOR.decode_json(cbor_encoded)
assert input == Jason.decode!(json_encoded)
{:ok, cbor_encoded} = Hexpds.DagCBOR.encode(Jason.encode!(input))
{:ok, original} = Hexpds.DagCBOR.decode(cbor_encoded)
assert input == original
end
end
end

0 comments on commit ed5f982

Please sign in to comment.