Skip to content

Commit bfcaa80

Browse files
committed
sphinx: add attributable error structure
We add the AttrErrorStructure which is going to be used in follow-up commits by the updated onion error encrypter and decrypter.
1 parent 187d0dd commit bfcaa80

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

attr_error_structure.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package sphinx
2+
3+
import (
4+
"crypto/hmac"
5+
"crypto/sha256"
6+
"io"
7+
)
8+
9+
// AttrErrorStructure contains the parameters that define the structure
10+
// of the error message that is passed back.
11+
type AttrErrorStructure struct {
12+
// hopCount is the assumed maximum number of hops in the path.
13+
hopCount int
14+
15+
// fixedPayloadLen is the length of the payload data that each hop along
16+
// the route can add.
17+
fixedPayloadLen int
18+
19+
// hmacSize is the number of bytes that is reserved for each hmac.
20+
hmacSize int
21+
22+
zeroHmac []byte
23+
}
24+
25+
// NewAttrErrorStructure creates an AttrErrorStructure with the defined
26+
// parameters and returns it.
27+
func NewAttrErrorStructure(hopCount int, fixedPayloadLen int,
28+
hmacSize int) *AttrErrorStructure {
29+
30+
return &AttrErrorStructure{
31+
hopCount: hopCount,
32+
fixedPayloadLen: fixedPayloadLen,
33+
hmacSize: hmacSize,
34+
35+
zeroHmac: make([]byte, hmacSize),
36+
}
37+
}
38+
39+
// HopCount returns the assumed maximum number of hops in the path.
40+
func (o *AttrErrorStructure) HopCount() int {
41+
return o.hopCount
42+
}
43+
44+
// FixedPayloadLen returns the length of the payload data that each hop along
45+
// the route can add.
46+
func (o *AttrErrorStructure) FixedPayloadLen() int {
47+
return o.fixedPayloadLen
48+
}
49+
50+
// HmacSize returns the number of bytes that is reserved for each hmac.
51+
func (o *AttrErrorStructure) HmacSize() int {
52+
return o.hmacSize
53+
}
54+
55+
// totalHmacs is the total number of hmacs that is present in the failure
56+
// message. Every hop adds HopCount hmacs to the message, but as the error
57+
// back-propagates, downstream hmacs can be pruned. This results in the number
58+
// of hmacs for each hop decreasing by one for each step that we move away from
59+
// the current node.
60+
func (o *AttrErrorStructure) totalHmacs() int {
61+
return (o.hopCount * (o.hopCount + 1)) / 2
62+
}
63+
64+
// allHmacsLen is the total length in the bytes of all hmacs in the failure
65+
// message.
66+
func (o *AttrErrorStructure) allHmacsLen() int {
67+
return o.totalHmacs() * o.hmacSize
68+
}
69+
70+
// hmacsAndPayloadsLen is the total length in bytes of all hmacs and payloads
71+
// together.
72+
func (o *AttrErrorStructure) hmacsAndPayloadsLen() int {
73+
return o.allHmacsLen() + o.allPayloadsLen()
74+
}
75+
76+
// allPayloadsLen is the total length in bytes of all payloads in the failure
77+
// message.
78+
func (o *AttrErrorStructure) allPayloadsLen() int {
79+
return o.payloadLen() * o.hopCount
80+
}
81+
82+
// payloadLen is the size of the per-node payload. It consists of a 1-byte
83+
// payload type followed by the payload data.
84+
func (o *AttrErrorStructure) payloadLen() int {
85+
return o.fixedPayloadLen
86+
}
87+
88+
// payloads returns a slice containing all payloads in the given failure
89+
// data block. The payloads follow the message in the block.
90+
func (o *AttrErrorStructure) payloads(data []byte) []byte {
91+
dataLen := len(data)
92+
93+
return data[dataLen-o.hmacsAndPayloadsLen() : dataLen-o.allHmacsLen()]
94+
}
95+
96+
// hmacs returns a slice containing all hmacs in the given failure data block.
97+
// The hmacs are positioned at the end of the data block.
98+
func (o *AttrErrorStructure) hmacs(data []byte) []byte {
99+
return data[len(data)-o.allHmacsLen():]
100+
}
101+
102+
// calculateHmac calculates an hmac given a shared secret and a presumed
103+
// position in the path. Position is expressed as the distance to the error
104+
// source. The error source itself is at position 0.
105+
func (o *AttrErrorStructure) calculateHmac(sharedSecret Hash256,
106+
position int, message, payloads, hmacs []byte) []byte {
107+
108+
umKey := generateKey("um", &sharedSecret)
109+
hash := hmac.New(sha256.New, umKey[:])
110+
111+
// Include message.
112+
_, _ = hash.Write(message)
113+
114+
// Include payloads including our own.
115+
_, _ = hash.Write(payloads[:(position+1)*o.payloadLen()])
116+
117+
// Include downstream hmacs.
118+
writeDownstreamHmacs(position, o.hopCount, hmacs, o.hmacSize, hash)
119+
120+
hmac := hash.Sum(nil)
121+
122+
return hmac[:o.hmacSize]
123+
}
124+
125+
// writeDownstreamHmacs writes the hmacs of downstream nodes that are relevant
126+
// for the given position to a writer instance. Position is expressed as the
127+
// distance to the error source. The error source itself is at position 0.
128+
func writeDownstreamHmacs(position, maxHops int, hmacs []byte, hmacBytes int,
129+
w io.Writer) {
130+
131+
// Track the index of the next hmac to write in a variable. The first
132+
// maxHops slots are reserved for the hmacs of the current hop and can
133+
// therefore be skipped. The first hmac to write is part of the block of
134+
// hmacs that was written by the first downstream node. Which hmac
135+
// exactly is determined by the assumed position of the current node.
136+
var hmacIdx = maxHops + (maxHops - position - 1)
137+
138+
// Iterate over all downstream nodes.
139+
for j := 0; j < position; j++ {
140+
_, _ = w.Write(
141+
hmacs[hmacIdx*hmacBytes : (hmacIdx+1)*hmacBytes],
142+
)
143+
144+
// Calculate the total number of hmacs in the block of the
145+
// current downstream node.
146+
blockSize := maxHops - j - 1
147+
148+
// Skip to the next block. The new hmac index will point to the
149+
// hmac that corresponds to the next downstream node which is
150+
// one step closer to the assumed error source.
151+
hmacIdx += blockSize
152+
}
153+
}

0 commit comments

Comments
 (0)