Browse Source

Adding explicit transcript to auth protocol

Sarah Jamie Lewis 6 months ago
2 changed files with 80 additions and 22 deletions
  1. 29
  2. 51

+ 29
- 22
applications/auth.go View File

@@ -8,8 +8,6 @@ import (

// AuthMessage is exchanged between peers to obtain the Auth Capability
@@ -59,36 +57,45 @@ func (ea AuthApp) Init(connection tapir.Connection) {
key := primitives.Perform3DH(connection.ID(), &ephemeralIdentity, remoteAuthMessage.LongTermPublicKey, remoteAuthMessage.EphemeralPublicKey, connection.IsOutbound())

// Wait to Sync (we need to ensure that both the Local and Remote server have turned encryption on
// otherwise our next Send will fail.
// We just successfully unmarshaled both of these, so we can safely ignore the err return from these functions.
challengeRemote, _ := json.Marshal(remoteAuthMessage)
challengeLocal, _ := json.Marshal(authMessage)

// TODO: Replace this with proper transcript primitive
challengeRemote, err := json.Marshal(remoteAuthMessage)
if err != nil {
challengeLocal, err := json.Marshal(authMessage)
if err != nil {
challenge := sha3.New512()
// Define canonical labels so both sides of the
var outboundAuthMessage []byte
var outboundHostname string
var inboundAuthMessage []byte
var inboundHostname string

if connection.IsOutbound() {
outboundHostname = connection.ID().Hostname()
inboundHostname = utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
outboundAuthMessage = challengeLocal
inboundAuthMessage = challengeRemote
} else {
outboundHostname = utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)
inboundHostname = connection.ID().Hostname()
outboundAuthMessage = challengeRemote
inboundAuthMessage = challengeLocal

// Derive a challenge from the transcript of the public parameters of this authentication protocol
var transcript *primitives.Transcript
transcript = primitives.NewTranscript("tapir-auth-" + outboundHostname + "-" + inboundHostname)
transcript.AddToTranscript("outbound-challenge", outboundAuthMessage)
transcript.AddToTranscript("inbound-challenge", inboundAuthMessage)
challengeBytes := transcript.CommitToTranscript("3dh-auth-challenge")

// If debug is turned on we will dump the transcript to log.
// There is nothing sensitive in this transcript
log.Debugf("Transcript: %s", transcript.OutputTranscriptToAudit())

// Since we have set the encryption key on the connection the connection will encrypt any messages we send with that key
// To test that the remote peer has done the same we calculate a challenge hash based on the transcript so far and send it to them
// We expect the remote to do the same, and compare the two.
// If successful we extend our auth capability to the connection and reassert the hostname.
challengeBytes := challenge.Sum([]byte{})
// We note that the only successful scenario here requires that the remote peer have successfully derived the same
// encryption key and the same transcript challenge.
remoteChallenge := connection.Expect()
if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 {

+ 51
- 0
primitives/transcript.go View File

@@ -0,0 +1,51 @@
package primitives

import (

// Transcript implements a transcript of a public coin argument.
// We have the following goals:
// - Provide a consisted transcript API for our zero knowledge protocols
// - Allow sequential proofs over a common transcript (ensuring a single proof cannot be extracted standalone)
// - produce an auditable human-readable transcript.
// The design of this API was inspired by Merlin:
// At some point we might want to extend this to be compatible with Merlin transcripts, built on STROBE
type Transcript struct {
hash hash.Hash
transcript string

// NewTranscript creates a new Transcript with the given Label, the label should be unique to the application
func NewTranscript(label string) *Transcript {
transcript := new(Transcript)
transcript.hash = sha3.New256()
transcript.AddToTranscript("protocol", []byte(label))
return transcript

// AddToTranscript appends a value to the transcript with the given label
// This binds the given data to the label.
func (t *Transcript) AddToTranscript(label string, b []byte) {
op := fmt.Sprintf("%s (%d) %x;", label, len(b), b)
t.transcript = fmt.Sprintf("%v\n%v", t.transcript, op)

// OutputTranscriptToAudit outputs a human-readable copy of the transcript so far.
func (t Transcript) OutputTranscriptToAudit() string {
return t.transcript

// CommitToTranscript generates a challenge based on the current transcript, it also commits the challenge to the transcript.
func (t *Transcript) CommitToTranscript(label string) []byte {
t.AddToTranscript("commit", []byte(label))
b := t.hash.Sum([]byte{})
t.AddToTranscript(label, b)
return b