lockstitch

package module
v0.0.0-...-aa4e13e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 13, 2026 License: Apache-2.0, MIT Imports: 10 Imported by: 0

README

Lockstitch

Lockstitch is an incremental, stateful cryptographic primitive for symmetric-key cryptographic operations (e.g., hashing, encryption, message authentication codes, and authenticated encryption) in complex protocols. Inspired by TupleHash, STROBE, Noise Protocol's stateful objects, Merlin transcripts, and Xoodyak's Cyclist mode, Lockstitch uses SHA-256, AES-128, and GMAC to provide 10+ Gb/sec performance on modern processors at a 128-bit security level.

CAUTION

⚠️ You should not use this. ⚠️

Neither the design nor the implementation of this library have been independently evaluated. The design is documented in design.md; read it and see if the arguments therein are convincing.

In addition, there is absolutely no guarantee of backwards compatibility.

Design

A Lockstitch protocol is a stateful object that has five different operations:

  • Init: Initializes a protocol with a domain separation string.
  • Mix: Mixes a piece of data into the protocol's state, making all future outputs dependent on it.
  • Derive: Outputs bytes of pseudo-random data dependent on the protocol's state.
  • Encrypt/Decrypt: Encrypts and decrypts data using the protocol's state as the key.
  • Seal/Open: Encrypts and decrypts data with authentication using the protocol's state as the key.

Using these operations, one can construct a wide variety of symmetric-key constructions.

Additional Information

For more information on the usage of Lockstitch, see the godoc.

For more information on the design of Lockstitch, see design.md.

License

Copyright © 2026 Coda Hale

Distributed under the Apache License 2.0 or MIT License.

Documentation

Overview

Package lockstitch provides an incremental, stateful cryptographic primitive for symmetric-key cryptographic operations (e.g., hashing, encryption, message authentication codes, and authenticated encryption) in complex protocols. Inspired by TupleHash, STROBE, Noise Protocol's stateful objects, Merlin transcripts, and Xoodyak's Cyclist mode, Lockstitch uses SHA-256, AES-128, and GMAC to provide 10+ Gb/sec performance on modern processors at a 128-bit security level.

Index

Examples

Constants

View Source
const (
	// TagSize is the number of bytes added to the plaintext by the Seal operation.
	TagSize = 16
	// MaxDeriveSize is the maximum number of bytes which can be produced by a single Protocol.Derive operation.
	MaxDeriveSize = 64 * 1024 * 1024 * 1024 // 64 GiB
)

Variables

View Source
var ErrInvalidCiphertext = errors.New("lockstitch: invalid ciphertext")

ErrInvalidCiphertext is returned when the ciphertext is invalid or has been decrypted with the wrong key.

Functions

This section is empty.

Types

type Protocol

type Protocol struct {
	// contains filtered or unexported fields
}

A Protocol is a stateful object providing fine-grained symmetric-key cryptographic services like hashing, message authentication codes, pseudo-random functions, authenticated encryption, and more.

Protocol instances are not concurrent-safe.

Example (Aead)
var ciphertext []byte
{
	// Initialize a protocol with a domain string.
	aead := lockstitch.NewProtocol("com.example.aead")

	// Mix the key into the protocol.
	key := []byte("my-secret-key")
	aead.Mix("key", key)

	// Mix the authenticated data into the protocol.
	ad := []byte("some authenticated data")
	aead.Mix("ad", ad)

	// Seal the plaintext.
	plaintext := []byte("hello world")
	ciphertext = aead.Seal("message", nil, plaintext)
	fmt.Printf("%x\n", ciphertext)
}

{
	// Initialize a protocol with a domain string.
	aead := lockstitch.NewProtocol("com.example.aead")

	// Mix the key into the protocol.
	key := []byte("my-secret-key")
	aead.Mix("key", key)

	// Mix the authenticated data into the protocol.
	ad := []byte("some authenticated data")
	aead.Mix("ad", ad)

	// Open the ciphertext.
	plaintext, err := aead.Open("message", nil, ciphertext)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s\n", plaintext)
}
Output:

fd58696cf39e4f44138a22a6ebaa374b6fe00fe0bce5f59e501d70
hello world
Example (Mac)
// Initialize a protocol with a domain string.
mac := lockstitch.NewProtocol("com.example.mac")

// Mix the key into the protocol.
key := []byte("my-secret-key")
mac.Mix("key", key)

// Mix the message into the protocol.
message := []byte("hello world")
mac.Mix("message", message)

// Derive 16 bytes of output.
// Note: The output length (128 bits) is encoded into the derivation, so
// changing the length will change the output.
tag := mac.Derive("tag", nil, 16)

fmt.Printf("%x\n", tag)
Output:

8072172e62079021ab8cdb8834071584
Example (Stream)
var ciphertext, nonce []byte
{
	// Initialize a protocol with a domain string.
	stream := lockstitch.NewProtocol("com.example.stream")

	// Mix the key into the protocol.
	key := []byte("my-secret-key")
	stream.Mix("key", key)

	// Mix a nonce into the protocol.
	nonce = []byte("actually random")
	stream.Mix("nonce", nonce)

	// Encrypt the plaintext.
	plaintext := []byte("hello world")
	ciphertext = stream.Encrypt("message", nil, plaintext)
	fmt.Printf("%x\n", ciphertext)
}

{
	// Initialize a protocol with a domain string.
	stream := lockstitch.NewProtocol("com.example.stream")

	// Mix the key into the protocol.
	key := []byte("my-secret-key")
	stream.Mix("key", key)

	// Mix a nonce into the protocol.
	nonce = []byte("actually random")
	stream.Mix("nonce", nonce)

	// Decrypt the ciphertext.
	plaintext := stream.Decrypt("message", nil, ciphertext)
	fmt.Printf("%s\n", plaintext)
}
Output:

342bcef28cbd46ac1b3cbc
hello world

func NewProtocol

func NewProtocol(domain string) *Protocol

NewProtocol creates a new Protocol with the given domain separation string.

The domain separation string should be unique to the application and specific protocol. It should not contain dynamic data like timestamps or user IDs. A good format is "application-name.protocol-name".

func (*Protocol) AppendBinary

func (p *Protocol) AppendBinary(b []byte) ([]byte, error)

func (*Protocol) Clone

func (p *Protocol) Clone() *Protocol

Clone returns an exact, deep copy of the receiver Protocol. The cloned protocol shares no state with the original.

func (*Protocol) Decrypt

func (p *Protocol) Decrypt(label string, dst, ciphertext []byte) []byte

Decrypt decrypts the given ciphertext using the protocol's current state as the key, then ratchets the protocol's state using the label and input. It appends the plaintext to dst and returns the resulting slice.

Decrypt provides confidentiality but not authenticity. If you need to ensure the ciphertext hasn't been modified, use Open instead.

To reuse ciphertext's storage for the decrypted output, use ciphertext[:0] as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext.

func (*Protocol) Derive

func (p *Protocol) Derive(label string, dst []byte, n int) []byte

Derive generates pseudorandom output from the Protocol's current state, the label, and the output length, then ratchets the Protocol's state with the label and output length. It appends the output to dst and returns the resulting slice.

Derive panics if n is negative or greater than MaxDeriveSize to strictly avoid birthday-bound attacks.

func (*Protocol) Encrypt

func (p *Protocol) Encrypt(label string, dst, plaintext []byte) []byte

Encrypt encrypts the plaintext using the protocol's current state as the key, then ratchets the protocol's state using the label and input. It appends the ciphertext to dst and returns the resulting slice.

Encrypt provides confidentiality but not authenticity. If you need to ensure the ciphertext hasn't been modified, use Seal instead.

To reuse plaintext's storage for the encrypted output, use plaintext[:0] as dst. Otherwise, the remaining capacity of dst must not overlap plaintext.

func (*Protocol) MarshalBinary

func (p *Protocol) MarshalBinary() (data []byte, err error)

func (*Protocol) Mix

func (p *Protocol) Mix(label string, input []byte)

Mix ratchets the protocol's state using the given label and input.

func (*Protocol) Open

func (p *Protocol) Open(label string, dst, ciphertext []byte) ([]byte, error)

Open decrypts the given slice in place using the protocol's current state as the key, verifying the final TagSize bytes as an authentication tag. If the ciphertext is authentic, it appends the plaintext to dst and returns the resulting slice; otherwise, ErrInvalidCiphertext is returned.

To reuse ciphertext's storage for the decrypted output, use ciphertext[:0] as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext.

func (*Protocol) Seal

func (p *Protocol) Seal(label string, dst, plaintext []byte) []byte

Seal encrypts the given plaintext using the protocol's current state as the key, appending an authentication tag of TagSize bytes, then ratchets the protocol's state using the label and input. It appends the ciphertext and authentication tag to dst and returns the resulting slice.

To reuse plaintext's storage for the encrypted output, use plaintext[:0] as dst. Otherwise, the remaining capacity of dst must not overlap plaintext.

func (*Protocol) UnmarshalBinary

func (p *Protocol) UnmarshalBinary(data []byte) error

Directories

Path Synopsis
internal
aes
Package aes provides concise implementations of AES-CTR for confidentiality and AES-GMAC for authenticity.
Package aes provides concise implementations of AES-CTR for confidentiality and AES-GMAC for authenticity.
tuplehash
Package tuplehash implements various routines from [NIST SP 800-185].
Package tuplehash implements various routines from [NIST SP 800-185].

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL