Documentation
¶
Overview ¶
Package newplex provides an incremental, stateful cryptographic primitive for symmetric-key cryptographic operations (e.g., hashing, encryption, message authentication codes, and authenticated encryption) in complex schemes. Inspired by TupleHash, STROBE, Noise Protocol's stateful objects, Merlin transcripts, DuplexWrap, and Xoodyak's Cyclist mode, Newplex uses the Simpira-1024 permutation to provide 10+ Gb/second performance on modern processors at a 128-bit security level.
On AMD64 and ARM64 architectures, newplex uses the AES-NI instruction set to achieve this level of performance. On other architectures, or if the purego build tag is used, it uses a much-slower Go implementation with a bit-sliced, constant-time AES round implementation.
Example ¶
package main
import (
"fmt"
"github.com/codahale/newplex"
)
func main() {
protocol := newplex.NewProtocol("com.example.kat")
protocol.Mix("first", []byte("one"))
protocol.Mix("second", []byte("two"))
third := protocol.Derive("third", nil, 8)
fmt.Printf("Derive('third', 8) = %x\n", third)
plaintext := []byte("this is an example")
ciphertext := protocol.Mask("fourth", nil, plaintext)
fmt.Printf("Mask('fourth', '%s') = %x\n", plaintext, ciphertext)
ciphertext = protocol.Seal("fifth", nil, []byte("this is an example"))
fmt.Printf("Seal('fifth', '%s') = %x\n", plaintext, ciphertext)
sixth := protocol.Derive("sixth", nil, 8)
fmt.Printf("Derive('sixth', 8) = %x\n", sixth)
}
Output: Derive('third', 8) = 78176ca0f97f7ba2 Mask('fourth', 'this is an example') = 02a181698f527c7818039cad5e65dc714655 Seal('fifth', 'this is an example') = ec9d04766b7fabc40ae93a5b6e950c5bf411faa962577543750b1ef7a4258e5e866f Derive('sixth', 8) = aa743b650db5458c
Index ¶
- Constants
- Variables
- type CryptStream
- type MixWriter
- type Protocol
- func (p *Protocol) AppendBinary(b []byte) ([]byte, error)
- func (p *Protocol) Clear()
- func (p *Protocol) Clone() *Protocol
- func (p *Protocol) Derive(label string, dst []byte, n int) []byte
- func (p *Protocol) Equal(p2 *Protocol) int
- func (p *Protocol) Fork(label string, leftValue, rightValue []byte) (left, right *Protocol)
- func (p *Protocol) ForkN(label string, values ...[]byte) []*Protocol
- func (p *Protocol) MarshalBinary() (data []byte, err error)
- func (p *Protocol) Mask(label string, dst, plaintext []byte) []byte
- func (p *Protocol) MaskStream(label string) *CryptStream
- func (p *Protocol) Mix(label string, input []byte)
- func (p *Protocol) MixReader(label string, r io.Reader) io.ReadCloser
- func (p *Protocol) MixWriter(label string, w io.Writer) *MixWriter
- func (p *Protocol) Open(label string, dst, ciphertextAndTag []byte) ([]byte, error)
- func (p *Protocol) Ratchet(label string)
- func (p *Protocol) Seal(label string, dst, plaintext []byte) []byte
- func (p *Protocol) String() string
- func (p *Protocol) UnmarshalBinary(data []byte) error
- func (p *Protocol) Unmask(label string, dst, ciphertext []byte) []byte
- func (p *Protocol) UnmaskStream(label string) *CryptStream
Examples ¶
Constants ¶
const TagSize = 16
TagSize is the number of bytes added to the plaintext by the Seal operation.
Variables ¶
var ErrInvalidCiphertext = errors.New("newplex: 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 CryptStream ¶
type CryptStream struct {
// contains filtered or unexported fields
}
CryptStream implements a streaming version of a protocol's Mask or Unmask operation.
N.B.: After the stream has been masked or unmasked, the caller MUST call Close to complete the operation.
func (*CryptStream) Close ¶
func (c *CryptStream) Close() error
Close ends the Mask or Unmask operation and marks the underlying protocol as available for other operations.
func (*CryptStream) XORKeyStream ¶
func (c *CryptStream) XORKeyStream(dst, src []byte)
XORKeyStream XORs each byte in the given slice with a byte from the cipher's key stream. Dst and src must overlap entirely or not at all.
If len(dst) < len(src), XORKeyStream should panic. It is acceptable to pass a dst bigger than src, and in that case, XORKeyStream will only update dst[:len(src)] and will not touch the rest of dst.
Multiple calls to XORKeyStream behave as if the concatenation of the src buffers was passed in a single run. That is, Stream maintains state and does not reset at each XORKeyStream call.
type MixWriter ¶
type MixWriter struct {
// contains filtered or unexported fields
}
MixWriter allows for the incremental processing of a stream of data into a single Mix operation on a protocol.
func (*MixWriter) Branch ¶
Branch returns a clone of the writer's protocol with the Mix operation completed. The original writer and protocol remain unmodified.
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, pseudorandom functions, authenticated encryption, and more.
Protocol instances are not concurrent-safe.
Example (Aead) ¶
package main
import (
"fmt"
"github.com/codahale/newplex"
)
func main() {
encrypt := func(key, nonce, ad, plaintext []byte) []byte {
// Initialize a protocol with a domain string.
aead := newplex.NewProtocol("com.example.aead")
// Mix the key and nonce into the protocol.
aead.Mix("key", key)
aead.Mix("nonce", nonce)
// Mix the authenticated data into the protocol.
aead.Mix("ad", ad)
// Seal the plaintext.
return aead.Seal("message", nil, plaintext)
}
decrypt := func(key, nonce, ad, ciphertext []byte) ([]byte, error) {
// Initialize a protocol with a domain string.
aead := newplex.NewProtocol("com.example.aead")
// Mix the key and nonce into the protocol.
aead.Mix("key", key)
aead.Mix("nonce", nonce)
// Mix the authenticated data into the protocol.
aead.Mix("ad", ad)
// Open the ciphertext.
return aead.Open("message", nil, ciphertext)
}
key := []byte("my-secret-key")
nonce := []byte("actually random")
ad := []byte("some authenticated data")
plaintext := []byte("hello world")
ciphertext := encrypt(key, nonce, ad, plaintext)
fmt.Printf("ciphertext = %x\n", ciphertext)
plaintext, err := decrypt(key, nonce, ad, ciphertext)
if err != nil {
panic(err)
}
fmt.Printf("plaintext = %s\n", plaintext)
}
Output: ciphertext = 3a8f6fa30018ff130831988969f1c4475a06ddddcd027cd4e707e3 plaintext = hello world
Example (Hpke) ¶
package main
import (
"crypto/ecdh"
"encoding/hex"
"fmt"
"github.com/codahale/newplex"
)
func main() {
encrypt := func(receiver *ecdh.PublicKey, plaintext []byte) []byte {
// This should be randomly generated, but it would make the test always fail.
ephemeralPrivBuf, _ := hex.DecodeString("a0b9a9ea71d45df9a8c7cf7da798c4394342993b21f24c7bb3612e573e8a58df")
ephemeral, _ := ecdh.X25519().NewPrivateKey(ephemeralPrivBuf)
// Initialize a protocol with a domain string.
hpke := newplex.NewProtocol("com.example.hpke")
// Mix the receiver's public key and the ephemeral public key into the protocol.
hpke.Mix("receiver", receiver.Bytes())
hpke.Mix("ephemeral", ephemeral.PublicKey().Bytes())
// Mix the ECDH shared secret into the protocol.
ss, err := ephemeral.ECDH(receiver)
if err != nil {
panic(err)
}
hpke.Mix("ecdh", ss)
// Seal the plaintext and append it to the ephemeral public key.
return hpke.Seal("message", ephemeral.PublicKey().Bytes(), plaintext)
}
decrypt := func(receiver *ecdh.PrivateKey, ciphertext []byte) ([]byte, error) {
ephemeral, err := ecdh.X25519().NewPublicKey(ciphertext[:32])
if err != nil {
panic(err)
}
hpke := newplex.NewProtocol("com.example.hpke")
hpke.Mix("receiver", receiver.PublicKey().Bytes())
hpke.Mix("ephemeral", ephemeral.Bytes())
ss, err := receiver.ECDH(ephemeral)
if err != nil {
panic(err)
}
hpke.Mix("ecdh", ss)
return hpke.Open("message", nil, ciphertext[32:])
}
receiverPrivBuf, _ := hex.DecodeString("c3a9b89b9a9a15da3c7a7e8ce9c96a828744abf52c0239f4180b0948fa3b1c74")
receiver, _ := ecdh.X25519().NewPrivateKey(receiverPrivBuf)
message := []byte("hello world")
ciphertext := encrypt(receiver.PublicKey(), message)
fmt.Printf("ciphertext = %x\n", ciphertext)
plaintext, err := decrypt(receiver, ciphertext)
if err != nil {
panic(err)
}
fmt.Printf("plaintext = %s\n", plaintext)
}
Output: ciphertext = 672e904ba78b50b56f896d4b9c2f8018aecfd34038523a6faa4e82e37be4281fbf994b02f43b7d778450dc5ca8a017b07cc18b32082e9160372940 plaintext = hello world
Example (Mac) ¶
package main
import (
"fmt"
"github.com/codahale/newplex"
)
func main() {
mac := func(key, message []byte) []byte {
// Initialize a protocol with a domain string.
mac := newplex.NewProtocol("com.example.mac")
// Mix the key into the protocol.
mac.Mix("key", key)
// Mix the message into the protocol.
mac.Mix("message", message)
// Derive 16 bytes of output.
// Note: The output length is encoded into the derivation, so changing the length will change the output.
tag := mac.Derive("tag", nil, 16)
return tag
}
key := []byte("my-secret-key")
message := []byte("hello world")
tag := mac(key, message)
fmt.Printf("tag = %x\n", tag)
}
Output: tag = 26ef9fb8d3008bba635559a8a1bde412
Example (Stream) ¶
package main
import (
"fmt"
"github.com/codahale/newplex"
)
func main() {
encrypt := func(key, nonce, plaintext []byte) []byte {
// Initialize a protocol with a domain string.
stream := newplex.NewProtocol("com.example.stream")
// Mix the key and nonce into the protocol.
stream.Mix("key", key)
stream.Mix("nonce", nonce)
// Encrypt the plaintext without any authenticity.
return stream.Mask("message", nil, plaintext)
}
decrypt := func(key, nonce, ciphertext []byte) []byte {
// Initialize a protocol with a domain string.
stream := newplex.NewProtocol("com.example.stream")
// Mix the key and nonce into the protocol.
stream.Mix("key", key)
stream.Mix("nonce", nonce)
// Decrypt the ciphertext.
return stream.Unmask("message", nil, ciphertext)
}
key := []byte("my-secret-key")
nonce := []byte("actually random")
plaintext := []byte("hello world")
ciphertext := encrypt(key, nonce, plaintext)
fmt.Printf("ciphertext = %x\n", ciphertext)
plaintext = decrypt(key, nonce, ciphertext)
fmt.Printf("plaintext = %s\n", plaintext)
}
Output: ciphertext = 45a0a3553319d4c0dc039e plaintext = hello world
func NewProtocol ¶
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 ¶
AppendBinary appends the binary representation of the protocol's state to the given slice. It implements encoding.BinaryAppender.
AppendBinary panics if a streaming operation is currently active.
func (*Protocol) Clone ¶
Clone returns a full clone of the receiver.
Clone panics if a streaming operation is currently active.
func (*Protocol) Derive ¶
Derive updates the protocol's state with the given label and output length and then generates n bytes of pseudorandom output. It appends the output to dst and returns the resulting slice.
Derive panics if n is negative or if a streaming operation is currently active.
func (*Protocol) Fork ¶
Fork returns two copies of the receiver, with the left side having absorbed the left value and the right side having absorbed the right.
func (*Protocol) ForkN ¶
ForkN returns N copies of the receiver, with each branch having absorbed the branch-specific value. The receiver is updated with a root-specific branch ID.
Panics if the number of branches is greater than 255.
func (*Protocol) MarshalBinary ¶
MarshalBinary returns the binary representation of the protocol's state. It implements encoding.BinaryMarshaler.
MarshalBinary panics if a streaming operation is currently active.
func (*Protocol) Mask ¶
Mask updates the protocol's state with the given label, then uses the state to encrypt the given plaintext. It appends the ciphertext to dst and returns the resulting slice.
Mask provides confidentiality but not authenticity. To ensure ciphertext authenticity, use Seal instead.
Ciphertexts produced by Mask do not depend on their length, so the ciphertexts for 'A' and 'AB' will share a prefix. To prevent this, include the message length in a prior Mix operation.
To reuse plaintext's storage for the encrypted output, use plaintext[:0] as dst. Otherwise, the remaining capacity of dst must not overlap plaintext.
Mask panics if a streaming operation is currently active.
func (*Protocol) MaskStream ¶
func (p *Protocol) MaskStream(label string) *CryptStream
MaskStream updates the protocol's state using the given label and returns a cipher.Stream which will mask any data passed to it. This can be used with cipher.StreamReader or cipher.StreamWriter to mask data during IO operations.
N.B.: The returned CryptStream must be closed for the Mask operation to be complete. While the returned CryptStream is open, any other operation on the Protocol will panic.
MaskStream panics if a streaming operation is currently active.
func (*Protocol) Mix ¶
Mix updates the protocol's state using the given label and input.
Mix panics if a streaming operation is currently active.
func (*Protocol) MixReader ¶
MixReader updates the protocol's state using the given label and whatever data is read from the wrapped io.Reader.
N.B.: The returned io.ReadCloser must be closed for the Mix operation to be complete. While the returned io.ReadCloser is open, any other operation on the Protocol will panic.
MixReader panics if a streaming operation is currently active.
func (*Protocol) MixWriter ¶
MixWriter updates the protocol's state using the given label and whatever data is written to the wrapped io.Writer.
N.B.: The returned io.WriteCloser must be closed for the Mix operation to be complete. While the returned io.WriteCloser is open, any other operation on the Protocol will panic.
MixWriter panics if a streaming operation is currently active.
func (*Protocol) Open ¶
Open updates the protocol's state with the given label and plaintext length, then uses the state to decrypt the given ciphertext, 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. Returns ErrInvalidCiphertext if the ciphertext is not authentic.
To reuse ciphertext's storage for the decrypted output, use ciphertext[:0] as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext.
WARNING: Open decrypts the ciphertext in-place before verifying the authentication tag. If the tag is invalid, the decrypted plaintext (which is now in dst) is zeroed out, but the original ciphertext is lost. To preserve the ciphertext in case of error, do not use in-place decryption (i.e., do not use ciphertext[:0] as dst).
Open panics if a streaming operation is currently active.
func (*Protocol) Ratchet ¶
Ratchet irreversibly modifies the protocol's state, preventing rollback and establishing forward secrecy.
func (*Protocol) Seal ¶
Seal updates the protocol's state with the given label and plaintext length, then uses the state to encrypt the given plaintext, appending an authentication tag of TagSize bytes. It appends the ciphertext 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.
Seal panics if a streaming operation is currently active.
func (*Protocol) String ¶
String returns a safe string representation of the protocol's state for debugging purposes.
func (*Protocol) UnmarshalBinary ¶
UnmarshalBinary restores the protocol's state from the given binary representation. It implements encoding.BinaryUnmarshaler.
UnmarshalBinary panics if a streaming operation is currently active.
func (*Protocol) Unmask ¶
Unmask updates the protocol's state with the given label, then uses the state to decrypt the given ciphertext. It appends the plaintext to dst and returns the resulting slice.
Unmask provides confidentiality but not authenticity. To ensure ciphertext authenticity, use Seal instead.
To reuse ciphertext's storage for the encrypted output, use ciphertext[:0] as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext.
Unmask panics if a streaming operation is currently active.
func (*Protocol) UnmaskStream ¶
func (p *Protocol) UnmaskStream(label string) *CryptStream
UnmaskStream updates the protocol's state using the given label and returns a cipher.Stream which will unmask any data passed to it. This can be used with cipher.StreamReader or cipher.StreamWriter to unmask data during IO operations.
N.B.: The returned CryptStream must be closed for the Unmask operation to be complete. While the returned CryptStream is open, any other operation on the Protocol will panic.
UnmaskStream panics if a streaming operation is currently active.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package adratchet implements an asynchronous double ratchet mechanism with Newplex and Ristretto255.
|
Package adratchet implements an asynchronous double ratchet mechanism with Newplex and Ristretto255. |
|
Package aead provides an implementation of Authenticated Encryption with Associated Data (AEAD) using the Newplex protocol.
|
Package aead provides an implementation of Authenticated Encryption with Associated Data (AEAD) using the Newplex protocol. |
|
Package aestream provides a streaming authenticated encryption scheme on top of a newplex.Protocol.
|
Package aestream provides a streaming authenticated encryption scheme on top of a newplex.Protocol. |
|
cmd
|
|
|
ae_proxy
command
Command ae_proxy is a Newplex/Ristretto255 authenticated encryption proxy which terminates handshake/aestream connections and makes plaintext connections.
|
Command ae_proxy is a Newplex/Ristretto255 authenticated encryption proxy which terminates handshake/aestream connections and makes plaintext connections. |
|
ae_reverse_proxy
command
Command ae_reverse_proxy is a Newplex/Ristretto255 authenticated encryption reverse proxy which terminates plaintext connections and makes handshake/aestream connections.
|
Command ae_reverse_proxy is a Newplex/Ristretto255 authenticated encryption reverse proxy which terminates plaintext connections and makes handshake/aestream connections. |
|
pt_connect
command
Command pt_connect makes a plaintext connection to a server, writes stdin to the server, and reads data to stdout.
|
Command pt_connect makes a plaintext connection to a server, writes stdin to the server, and reads data to stdout. |
|
pt_echo
command
Command pt_echo listens for plaintext connections, reads data, and writes the same data back.
|
Command pt_echo listens for plaintext connections, reads data, and writes the same data back. |
|
Package digest provides an implementation of a message digest (hash) using the Newplex protocol.
|
Package digest provides an implementation of a message digest (hash) using the Newplex protocol. |
|
Package frost implements FROST (Flexible Round-Optimized Schnorr Threshold) signatures using Ristretto255 and Newplex.
|
Package frost implements FROST (Flexible Round-Optimized Schnorr Threshold) signatures using Ristretto255 and Newplex. |
|
Package handshake implements a mutually-authenticated static-ephemeral handshake using Ristretto255 and Newplex.
|
Package handshake implements a mutually-authenticated static-ephemeral handshake using Ristretto255 and Newplex. |
|
Package hpke implements a hybrid public key encryption (HPKE) scheme.
|
Package hpke implements a hybrid public key encryption (HPKE) scheme. |
|
internal
|
|
|
duplex
Package duplex implements a cryptographic duplex construction using the Simpira-1024 permutation.
|
Package duplex implements a cryptographic duplex construction using the Simpira-1024 permutation. |
|
simpira1024
Package simpira1024 provides an implementation of the Simpira-1024 permutation, also known as [Simpira b=8 V2].
|
Package simpira1024 provides an implementation of the Simpira-1024 permutation, also known as [Simpira b=8 V2]. |
|
Package mhf implements the DEGSample data-dependent memory-hard function from Blocki & Holman (2025), "Towards Practical Data-Dependent Memory-Hard Functions with Optimal Sustained Space Trade-offs."
[DEGSample]: https://arxiv.org/pdf/2508.06795
|
Package mhf implements the DEGSample data-dependent memory-hard function from Blocki & Holman (2025), "Towards Practical Data-Dependent Memory-Hard Functions with Optimal Sustained Space Trade-offs." [DEGSample]: https://arxiv.org/pdf/2508.06795 |
|
Package oae2 provides an Online Authenticated Encryption (OAE2) stream implementation.
|
Package oae2 provides an Online Authenticated Encryption (OAE2) stream implementation. |
|
Package oprf implements an [RFC 9497]-style Oblivious Pseudorandom Function scheme using Newplex and Ristretto255.
|
Package oprf implements an [RFC 9497]-style Oblivious Pseudorandom Function scheme using Newplex and Ristretto255. |
|
Package pake provides a [Cpace]-style Password-Authenticated Key Exchange (PAKE), which allows two parties which share a possibly low-entropy secret (like a password) to establish a high-entropy shared protocol state for e.g.
|
Package pake provides a [Cpace]-style Password-Authenticated Key Exchange (PAKE), which allows two parties which share a possibly low-entropy secret (like a password) to establish a high-entropy shared protocol state for e.g. |
|
Package sig implements an EdDSA-style Schnorr digital signature scheme using Ristretto255 and Newplex.
|
Package sig implements an EdDSA-style Schnorr digital signature scheme using Ristretto255 and Newplex. |
|
Package signcrypt implements an integrated signcryption scheme using Ristretto255 and Newplex.
|
Package signcrypt implements an integrated signcryption scheme using Ristretto255 and Newplex. |
|
Package siv implements a Synthetic Initialization Vector (SIV) AEAD scheme.
|
Package siv implements a Synthetic Initialization Vector (SIV) AEAD scheme. |
|
Package vrf implements a verifiable random function (VRF) using Ristretto255 and Newplex.
|
Package vrf implements a verifiable random function (VRF) using Ristretto255 and Newplex. |