forest-go: git.sr.ht/~whereswaldon/forest-go Index | Files | Directories

package forest

import "git.sr.ht/~whereswaldon/forest-go"

Package forest is a library for creating nodes in the Arbor Forest data structure.

The specification for the Arbor Forest can be found here: https://github.com/arborchat/protocol/blob/forest/spec/Forest.md

NOTE: this package requires using a fork of golang.org/x/crypto, and you must therefore include the following in your go.mod:

replace golang.org/x/crypto => github.com/ProtonMail/crypto <version-from-forest-go's-go.mod>

All nodes in the Arbor Forest are cryptographically signed by an Identity node. Identity nodes sign themselves. To create a new identity, first create or load an OpenPGP private key using golang.org/x/crypto/openpgp. Then you can use that key and name to create an identity.

privkey := getPrivateKey() // do this however
name, err := fields.NewQualifiedContent(fields.ContentTypeUTF8, "example")
// handle error
metadata, err := fields.NewQualifiedContent(fields.ContentTypeJSON, "{}")
// handle error
identity, err := forest.NewIdentity(privkey, name, metadata)
// handle error

Identities (and their private keys) can be used to create other nodes with the Builder type. You can create community nodes using a builder like so:

builder := forest.As(identity, privkey)
communityName, err := fields.NewQualifiedContent(fields.ContentTypeUTF8, "example")
// handle error
communityMetadata, err := fields.NewQualifiedContent(fields.ContentTypeJSON, "{}")
// handle error
community, err := builder.NewCommunity(communityName, communityMetadata)
// handle error

Builders can also create reply nodes:

message, err := fields.NewQualifiedContent(fields.ContentTypeUTF8, "example")
// handle error
replyMetadata, err := fields.NewQualifiedContent(fields.ContentTypeJSON, "{}")
// handle error
reply, err := builder.NewReply(community, message, replyMetadata)
// handle error
message2, err := fields.NewQualifiedContent(fields.ContentTypeUTF8, "reply to reply")
// handle error
reply2, err := builder.NewReply(reply, message2, replyMetadata)
// handle error

The Builder type can also be used fluently like so:

// omitting creating the qualified content and error handling
community, err := forest.As(identity, privkey).NewCommunity(communityName, communityMetadata)
reply, err := forest.As(identity, privkey).NewReply(community, message, replyMetadata)
reply2, err := forest.As(identity, privkey).NewReply(reply, message2, replyMetadata)

Index

Package Files

builder.go doc.go hashable.go nodes.go signature_validator.go store.go

Constants

const MaxNameLength = 256

func FindGPG Uses

func FindGPG() (path string, err error)

FindGPG returns the path to the local gpg executable if one can be found. Otherwise it returns an error.

func NodeTypeOf Uses

func NodeTypeOf(b []byte) (fields.NodeType, error)

NodeTypeOf returns the NodeType of the provided binary-marshaled node. If the provided bytes are not a forest node or the type cannot be determined, an error will be returned and the first return value must be ignored.

func ValidateID Uses

func ValidateID(h Hashable, expected fields.QualifiedHash) (bool, error)

ValidateID returns whether the ID of this commonNode matches the data. The first return value indicates the result of the comparison. If there is an error, the first return value will always be false and the second will indicate what went wrong when computing the hash.

func ValidateSignature Uses

func ValidateSignature(v SignatureValidator, identity *Identity) (bool, error)

ValidateSignature returns whether the signature contained in this SignatureValidator is a valid signature for the given Identity. When validating an Identity node, you should pass the same Identity as the second parameter.

func VersionAndNodeTypeOf Uses

func VersionAndNodeTypeOf(b []byte) (fields.Version, fields.NodeType, error)

type Builder Uses

type Builder struct {
    User *Identity
    Signer
}

Builder creates nodes in the forest on behalf of the given user.

func As Uses

func As(user *Identity, signer Signer) *Builder

As creates a Builder that can write new nodes on behalf of the provided user. It is intended to be able to be used fluently, like:

community, err := forest.As(user, privkey).NewCommunity(name, metatdata)

func (*Builder) NewCommunity Uses

func (n *Builder) NewCommunity(name string, metadata []byte) (*Community, error)

NewCommunity creates a community node (signed by the given identity with the given privkey).

func (*Builder) NewCommunityQualified Uses

func (n *Builder) NewCommunityQualified(name *fields.QualifiedContent, metadata *fields.QualifiedContent) (*Community, error)

func (*Builder) NewReply Uses

func (n *Builder) NewReply(parent interface{}, content string, metadata []byte) (*Reply, error)

NewReply creates a reply node as a child of the given community or reply

func (*Builder) NewReplyQualified Uses

func (n *Builder) NewReplyQualified(parent interface{}, content, metadata *fields.QualifiedContent) (*Reply, error)

type CommonNode Uses

type CommonNode struct {
    SchemaInfo `arbor:"order=0,recurse=always"`
    Parent     fields.QualifiedHash    `arbor:"order=1,recurse=serialize"`
    IDDesc     fields.HashDescriptor   `arbor:"order=2,recurse=always"`
    Depth      fields.TreeDepth        `arbor:"order=3"`
    Created    fields.Timestamp        `arbor:"order=4"`
    Metadata   fields.QualifiedContent `arbor:"order=5,recurse=serialize"`
    Author     fields.QualifiedHash    `arbor:"order=6,recurse=serialize"`
    // contains filtered or unexported fields
}

generic node

func (*CommonNode) Equals Uses

func (n *CommonNode) Equals(n2 *CommonNode) bool

func (CommonNode) HashDescriptor Uses

func (n CommonNode) HashDescriptor() *fields.HashDescriptor

func (CommonNode) ID Uses

func (n CommonNode) ID() *fields.QualifiedHash

Compute and return the CommonNode's ID as a fields.Qualified Hash

func (CommonNode) IsIdentity Uses

func (n CommonNode) IsIdentity() bool

func (CommonNode) ParentID Uses

func (n CommonNode) ParentID() *fields.QualifiedHash

func (*CommonNode) SignatureIdentityHash Uses

func (n *CommonNode) SignatureIdentityHash() *fields.QualifiedHash

SignatureIdentityHash returns the node identitifer for the Identity that signed this node.

func (CommonNode) TreeDepth Uses

func (n CommonNode) TreeDepth() fields.TreeDepth

func (*CommonNode) TwigMetadata Uses

func (n *CommonNode) TwigMetadata() (*twig.Data, error)

TwigMetadata returns the metadata of this node parsed into a *twig.Data

func (*CommonNode) ValidateDeep Uses

func (n *CommonNode) ValidateDeep(store Store) error

ValidateDeep checks for the existence of all referenced nodes within the provided store.

func (*CommonNode) ValidateShallow Uses

func (n *CommonNode) ValidateShallow() error

ValidateShallow checks all fields for internal validity. It does not check the existence or validity of nodes referenced from this node.

type Community Uses

type Community struct {
    CommonNode `arbor:"order=0,recurse=always"`
    Name       fields.QualifiedContent `arbor:"order=1,recurse=serialize"`
    Trailer    `arbor:"order=2,recurse=always"`
}

func UnmarshalCommunity Uses

func UnmarshalCommunity(b []byte) (*Community, error)

func (*Community) Equals Uses

func (c *Community) Equals(other interface{}) bool

func (*Community) MarshalBinary Uses

func (c *Community) MarshalBinary() ([]byte, error)

func (*Community) MarshalSignedData Uses

func (c *Community) MarshalSignedData() ([]byte, error)

func (*Community) UnmarshalBinary Uses

func (c *Community) UnmarshalBinary(b []byte) error

func (*Community) ValidateDeep Uses

func (c *Community) ValidateDeep(store Store) error

ValidateDeep checks all referenced nodes for existence within the store.

func (*Community) ValidateShallow Uses

func (c *Community) ValidateShallow() error

ValidateShallow checks all fields for internal validity. It does not check the existence or validity of nodes referenced from this node.

type GPGSigner Uses

type GPGSigner struct {
    GPGUserName string
    // Rewriter is invoked on each invocation of exec.Command that spawns GPG. You can use it to modify
    // flags or any other property of the subcommand (environment variables). This is especially useful
    // to control how GPG prompts for key passphrases.
    Rewriter func(*exec.Cmd) error
    // contains filtered or unexported fields
}

GPGSigner uses a local gpg2 installation for key management. It will invoke gpg2 as a subprocess to sign data and to acquire the public key for its signing key. The public fields can be used to modify its behavior in order to change how it prompts for passphrases and other details.

func NewGPGSigner Uses

func NewGPGSigner(gpgUserName string) (*GPGSigner, error)

NewGPGSigner wraps the private key so that it can sign using the local system's implementation of GPG.

func (GPGSigner) PublicKey Uses

func (s GPGSigner) PublicKey() ([]byte, error)

PublicKey returns the bytes of the OpenPGP public key used by this signer.

func (*GPGSigner) Sign Uses

func (s *GPGSigner) Sign(data []byte) ([]byte, error)

Sign invokes gpg2 to sign the data as this Signer's configured PGP user. It returns the signature or an error (if any).

type Hashable Uses

type Hashable interface {
    HashDescriptor() *fields.HashDescriptor
    encoding.BinaryMarshaler
}

type Identity Uses

type Identity struct {
    CommonNode `arbor:"order=0,recurse=always"`
    Name       fields.QualifiedContent `arbor:"order=1,recurse=serialize"`
    PublicKey  fields.QualifiedKey     `arbor:"order=2,recurse=serialize"`
    Trailer    `arbor:"order=3,recurse=always"`
}

Identity nodes represent a user. They associate a username with a public key that the user will sign messages with.

func NewIdentity Uses

func NewIdentity(signer Signer, name string, metadata []byte) (*Identity, error)

NewIdentity builds an Identity node for the user with the given name and metadata, using the OpenPGP Entity privkey to define the Identity. That Entity must contain a private key with no passphrase.

func NewIdentityQualified Uses

func NewIdentityQualified(signer Signer, name *fields.QualifiedContent, metadata *fields.QualifiedContent) (*Identity, error)

func UnmarshalIdentity Uses

func UnmarshalIdentity(b []byte) (*Identity, error)

func (*Identity) Equals Uses

func (i *Identity) Equals(other interface{}) bool

func (*Identity) MarshalBinary Uses

func (i *Identity) MarshalBinary() ([]byte, error)

func (*Identity) MarshalSignedData Uses

func (i *Identity) MarshalSignedData() ([]byte, error)

MarshalSignedData writes all data that should be signed in the correct order for signing. This can be used both to generate and validate message signatures.

func (*Identity) UnmarshalBinary Uses

func (i *Identity) UnmarshalBinary(b []byte) error

func (*Identity) ValidateDeep Uses

func (i *Identity) ValidateDeep(store Store) error

ValidateDeep checks all referenced nodes for existence within the store.

func (*Identity) ValidateShallow Uses

func (i *Identity) ValidateShallow() error

ValidateShallow checks all fields for internal validity. It does not check the existence or validity of nodes referenced from this node.

type NativeSigner Uses

type NativeSigner openpgp.Entity

NativeSigner uses golang's native openpgp operation for signing data. It only supports private keys without a passphrase.

func (NativeSigner) PublicKey Uses

func (s NativeSigner) PublicKey() ([]byte, error)

PublicKey returns the raw bytes of the binary openpgp public key used by this signer.

func (NativeSigner) Sign Uses

func (s NativeSigner) Sign(data []byte) ([]byte, error)

Sign signs the input data with the contained private key and returns the resulting signature.

type Node Uses

type Node interface {
    ID() *fields.QualifiedHash
    ParentID() *fields.QualifiedHash
    Equals(interface{}) bool
    TreeDepth() fields.TreeDepth
    ValidateShallow() error
    ValidateDeep(Store) error
    encoding.BinaryMarshaler
    encoding.BinaryUnmarshaler
}

func UnmarshalBinaryNode Uses

func UnmarshalBinaryNode(b []byte) (Node, error)

UnmarshalBinaryNode unmarshals a node of any type. If it does not return an error, the concrete type of the first return parameter will be one of the node structs declared in this package (e.g. Identity, Community, etc...)

type Reply Uses

type Reply struct {
    CommonNode     `arbor:"order=0,recurse=always"`
    CommunityID    fields.QualifiedHash    `arbor:"order=1,recurse=serialize"`
    ConversationID fields.QualifiedHash    `arbor:"order=2,recurse=serialize"`
    Content        fields.QualifiedContent `arbor:"order=3,recurse=serialize"`
    Trailer        `arbor:"order=4,recurse=always"`
}

func UnmarshalReply Uses

func UnmarshalReply(b []byte) (*Reply, error)

func (*Reply) Equals Uses

func (r *Reply) Equals(other interface{}) bool

func (*Reply) MarshalBinary Uses

func (r *Reply) MarshalBinary() ([]byte, error)

func (*Reply) MarshalSignedData Uses

func (r *Reply) MarshalSignedData() ([]byte, error)

func (*Reply) UnmarshalBinary Uses

func (r *Reply) UnmarshalBinary(b []byte) error

func (*Reply) ValidateDeep Uses

func (r *Reply) ValidateDeep(store Store) error

ValidateDeep checks all referenced nodes for existence within the store.

func (*Reply) ValidateShallow Uses

func (r *Reply) ValidateShallow() error

ValidateShallow checks all fields for internal validity. It does not check the existence or validity of nodes referenced from this node.

type SchemaInfo Uses

type SchemaInfo struct {
    Version fields.Version  `arbor:"order=0"`
    Type    fields.NodeType `arbor:"order=1"`
}

type SignatureValidator Uses

type SignatureValidator interface {
    MarshalSignedData() ([]byte, error)
    GetSignature() *fields.QualifiedSignature
    SignatureIdentityHash() *fields.QualifiedHash
    IsIdentity() bool
}

SignatureValidator is a type that has a signature and can supply the ID of the node that signed it.

type Signer Uses

type Signer interface {
    Sign(data []byte) (signature []byte, err error)
    PublicKey() (key []byte, err error)
}

Signer can sign any binary data

func NewNativeSigner Uses

func NewNativeSigner(privatekey *openpgp.Entity) (Signer, error)

NewNativeSigner creates a native Golang PGP signer. This will fail if the provided key is encrypted. GPGSigner should be used for all encrypted keys.

type Store Uses

type Store interface {
    CopyInto(Store) error
    Get(*fields.QualifiedHash) (Node, bool, error)
    GetIdentity(*fields.QualifiedHash) (Node, bool, error)
    GetCommunity(*fields.QualifiedHash) (Node, bool, error)
    GetConversation(communityID, conversationID *fields.QualifiedHash) (Node, bool, error)
    GetReply(communityID, conversationID, replyID *fields.QualifiedHash) (Node, bool, error)
    Children(*fields.QualifiedHash) ([]*fields.QualifiedHash, error)
    Recent(nodeType fields.NodeType, quantity int) ([]Node, error)
    // Add inserts a node into the store. It is *not* an error to insert a node which is already
    // stored. Implementations must not return an error in this case.
    Add(Node) error
}

type Trailer Uses

type Trailer struct {
    Signature fields.QualifiedSignature `arbor:"order=0,recurse=serialize,signature"`
}

Trailer is the final set of fields in every arbor node

func (*Trailer) Equals Uses

func (t *Trailer) Equals(t2 *Trailer) bool

func (*Trailer) GetSignature Uses

func (t *Trailer) GetSignature() *fields.QualifiedSignature

GetSignature returns the signature for the node, which must correspond to the Signature Authority for the node in order to be valid.

type Validator Uses

type Validator interface {
    Validate() error
}

Directories

PathSynopsis
archivePackage archive defines a helpful wrapper type to augment the store interface with higher-level methods for querying ancestry and descendants of nodes in the store.
fields
grovePackage grove implements an on-disk storage format for arbor forest nodes.
serialize
testkeysPackage testkeys provides PGP private keys SUITABLE ONLY FOR WRITING TEST CASES.
testutilPackage testutil provides utilities for easily making test arbor nodes and content.
twigPackage twig implements the twig key-value data format.

Package forest imports 14 packages (graph) and is imported by 5 packages. Updated 2020-07-14. Refresh now. Tools for package owners.