Documentation ¶
Overview ¶
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 ¶
- Constants
- func FindGPG() (path string, err error)
- func Is(nt fields.NodeType, n Node) bool
- func NodeTypeOf(b []byte) (fields.NodeType, error)
- func ValidateID(h Hashable, expected fields.QualifiedHash) (bool, error)
- func ValidateNode(n Node, store Store) error
- func ValidateSignature(v SignatureValidator, identity *Identity) (bool, error)
- func VersionAndNodeTypeOf(b []byte) (fields.Version, fields.NodeType, error)
- type Builder
- func (n *Builder) NewCommunity(name string, metadata []byte) (*Community, error)
- func (n *Builder) NewCommunityQualified(name *fields.QualifiedContent, metadata *fields.QualifiedContent) (*Community, error)
- func (n *Builder) NewReply(parent interface{}, content string, metadata []byte) (*Reply, error)
- func (n *Builder) NewReplyQualified(parent interface{}, content, metadata *fields.QualifiedContent) (*Reply, error)
- func (b *Builder) Now() time.Time
- type CommonNode
- func (n *CommonNode) AuthorID() *fields.QualifiedHash
- func (n CommonNode) CreatedAt() time.Time
- func (n *CommonNode) Equals(n2 *CommonNode) bool
- func (n CommonNode) HashDescriptor() *fields.HashDescriptor
- func (n CommonNode) ID() *fields.QualifiedHash
- func (n CommonNode) IsIdentity() bool
- func (n CommonNode) ParentID() *fields.QualifiedHash
- func (n *CommonNode) SignatureIdentityHash() *fields.QualifiedHash
- func (n CommonNode) TreeDepth() fields.TreeDepth
- func (n *CommonNode) TwigMetadata() (*twig.Data, error)
- func (n *CommonNode) ValidateInternal() error
- func (n *CommonNode) ValidateReferences(store Store) error
- type Community
- func (c *Community) Equals(other interface{}) bool
- func (c *Community) MarshalBinary() ([]byte, error)
- func (c *Community) MarshalSignedData() ([]byte, error)
- func (c *Community) UnmarshalBinary(b []byte) error
- func (c *Community) ValidateInternal() error
- func (c *Community) ValidateReferences(store Store) error
- type Copiable
- type GPGSigner
- type Hashable
- type Identity
- func (i *Identity) Equals(other interface{}) bool
- func (i *Identity) MarshalBinary() ([]byte, error)
- func (i *Identity) MarshalSignedData() ([]byte, error)
- func (i *Identity) UnmarshalBinary(b []byte) error
- func (i *Identity) ValidateInternal() error
- func (i *Identity) ValidateReferences(store Store) error
- type NativeSigner
- type Node
- type Paginated
- type Reply
- type SchemaInfo
- type SignatureValidator
- type Signer
- type Store
- type Trailer
- type Validator
Constants ¶
const MaxNameLength = 256
Variables ¶
This section is empty.
Functions ¶
func FindGPG ¶
FindGPG returns the path to the local gpg executable if one can be found. Otherwise it returns an error.
func NodeTypeOf ¶
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 ¶
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 ValidateNode ¶
ValidateNode calls ValidateInternal and ValidateReferences on a node, returning the first error encountered (or nil if there were no errors).
func ValidateSignature ¶
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.
Types ¶
type Builder ¶
Builder creates nodes in the forest on behalf of the given user.
func As ¶
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 ¶
NewCommunity creates a community node (signed by the given identity with the given privkey).
func (*Builder) NewCommunityQualified ¶
func (n *Builder) NewCommunityQualified(name *fields.QualifiedContent, metadata *fields.QualifiedContent) (*Community, error)
func (*Builder) NewReplyQualified ¶
func (n *Builder) NewReplyQualified(parent interface{}, content, metadata *fields.QualifiedContent) (*Reply, error)
type CommonNode ¶
type CommonNode struct { // Deterministically computed from the rest of the values. Identifier fields.Blob 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"` }
generic node
func (*CommonNode) AuthorID ¶
func (n *CommonNode) AuthorID() *fields.QualifiedHash
AuthorID returns the Author of a common node. It wraps SignatureIdentityHash to satisfy the Node interface.
func (CommonNode) CreatedAt ¶
func (n CommonNode) CreatedAt() time.Time
func (*CommonNode) Equals ¶
func (n *CommonNode) Equals(n2 *CommonNode) bool
func (CommonNode) HashDescriptor ¶
func (n CommonNode) HashDescriptor() *fields.HashDescriptor
func (CommonNode) ID ¶
func (n CommonNode) ID() *fields.QualifiedHash
Compute and return the CommonNode's ID as a fields.Qualified Hash
func (CommonNode) IsIdentity ¶
func (n CommonNode) IsIdentity() bool
func (CommonNode) ParentID ¶
func (n CommonNode) ParentID() *fields.QualifiedHash
func (*CommonNode) SignatureIdentityHash ¶
func (n *CommonNode) SignatureIdentityHash() *fields.QualifiedHash
SignatureIdentityHash returns the node identitifer for the Identity that signed this node.
func (CommonNode) TreeDepth ¶
func (n CommonNode) TreeDepth() fields.TreeDepth
func (*CommonNode) TwigMetadata ¶
func (n *CommonNode) TwigMetadata() (*twig.Data, error)
TwigMetadata returns the metadata of this node parsed into a *twig.Data
func (*CommonNode) ValidateInternal ¶
func (n *CommonNode) ValidateInternal() error
ValidateInternal checks all fields for internal validity. It does not check the existence or validity of nodes referenced from this node. If the node validates, ValidateInternal returns `nil`.
func (*CommonNode) ValidateReferences ¶
func (n *CommonNode) ValidateReferences(store Store) error
ValidateReferences checks for the existence of all referenced nodes within the provided store.
type Community ¶
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 ¶
func (*Community) MarshalBinary ¶
func (*Community) MarshalSignedData ¶
func (*Community) UnmarshalBinary ¶
func (*Community) ValidateInternal ¶
ValidateInternal checks all fields for internal validity. It does not check the existence or validity of nodes referenced from this node.
func (*Community) ValidateReferences ¶
ValidateReferences checks all referenced nodes for existence within the store.
type GPGSigner ¶
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 ¶
NewGPGSigner wraps the private key so that it can sign using the local system's implementation of GPG.
type Hashable ¶
type Hashable interface { HashDescriptor() *fields.HashDescriptor encoding.BinaryMarshaler }
type Identity ¶
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 ¶
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 ¶
func NewIdentityQualified(signer Signer, name *fields.QualifiedContent, metadata *fields.QualifiedContent) (*Identity, error)
func UnmarshalIdentity ¶
func (*Identity) MarshalBinary ¶
func (*Identity) MarshalSignedData ¶
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 ¶
func (*Identity) ValidateInternal ¶
ValidateInternal checks all fields for internal validity. It does not check the existence or validity of nodes referenced from this node.
func (*Identity) ValidateReferences ¶
ValidateReferences checks all referenced nodes for existence within the store.
type NativeSigner ¶
NativeSigner uses golang's native openpgp operation for signing data. It only supports private keys without a passphrase.
func (NativeSigner) PublicKey ¶
func (s NativeSigner) PublicKey() ([]byte, error)
PublicKey returns the raw bytes of the binary openpgp public key used by this signer.
type Node ¶
type Node interface { AuthorID() *fields.QualifiedHash CreatedAt() time.Time Equals(interface{}) bool ID() *fields.QualifiedHash ParentID() *fields.QualifiedHash TreeDepth() fields.TreeDepth TwigMetadata() (*twig.Data, error) ValidateReferences(Store) error ValidateInternal() error encoding.BinaryMarshaler encoding.BinaryUnmarshaler }
func UnmarshalBinaryNode ¶
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 Paginated ¶
type Paginated interface { // ChildrenBatched allows paging through the children of the node in fixed-size batches. // Iteration order is youngest first. ChildrenBatched( root *fields.QualifiedHash, quantity, offset int, ) (batch []*fields.QualifiedHash, total int, err error) // RecentFrom allows paging through nodes in fixed-size batches, starting from the given // timestamp. // Iteration order is youngest first. RecentFrom(nt fields.NodeType, ts time.Time, quantity int) ([]Node, error) }
Paginated stores can page through nodes with a series of queries.
type Reply ¶
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 ¶
func (*Reply) MarshalBinary ¶
func (*Reply) MarshalSignedData ¶
func (*Reply) UnmarshalBinary ¶
func (*Reply) ValidateInternal ¶
ValidateInternal checks all fields for internal validity. It does not check the existence or validity of nodes referenced from this node.
func (*Reply) ValidateReferences ¶
ValidateReferences checks all referenced nodes for existence within the store.
type SchemaInfo ¶
type SignatureValidator ¶
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 ¶
type Signer interface { Sign(data []byte) (signature []byte, err error) PublicKey() (key []byte, err error) }
Signer can sign any binary data
type Store ¶
type Store interface { // Get retrieves a node by ID. Get(*fields.QualifiedHash) (Node, bool, error) // GetIdentity retrieves an identity node by ID. GetIdentity(*fields.QualifiedHash) (Node, bool, error) // GetCommunity retrieves a community node by ID. GetCommunity(*fields.QualifiedHash) (Node, bool, error) // GetConversation retrieves a conversation node by ID. GetConversation(communityID, conversationID *fields.QualifiedHash) (Node, bool, error) // GetReply retrieves a reply node by ID. GetReply(communityID, conversationID, replyID *fields.QualifiedHash) (Node, bool, error) // Children returns a list of child nodes for the given node ID. Children(*fields.QualifiedHash) ([]*fields.QualifiedHash, error) // Recent returns recently-created (as per the timestamp in the node) nodes. // It may return both a slice of nodes and an error if some nodes in the // store were unreadable. 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 // RemoveSubtree from the store. RemoveSubtree(*fields.QualifiedHash) error }
Store describes a collection of `forest.Node`.
type Trailer ¶
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) GetSignature ¶
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.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
cmd
|
|
Package grove implements an on-disk storage format for arbor forest nodes.
|
Package grove implements an on-disk storage format for arbor forest nodes. |
Package orchard implements a boltdb backed on-disk node store, satisfying the forest.Store interface.
|
Package orchard implements a boltdb backed on-disk node store, satisfying the forest.Store interface. |
internal/mock
Package mock uses mocked structures to filter out non-essential data to clarify testing.
|
Package mock uses mocked structures to filter out non-essential data to clarify testing. |
Package testkeys provides PGP private keys SUITABLE ONLY FOR WRITING TEST CASES.
|
Package testkeys provides PGP private keys SUITABLE ONLY FOR WRITING TEST CASES. |
Package testutil provides utilities for easily making test arbor nodes and content.
|
Package testutil provides utilities for easily making test arbor nodes and content. |
Package twig implements the twig key-value data format.
|
Package twig implements the twig key-value data format. |