-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathseed256.go
More file actions
178 lines (164 loc) · 6.18 KB
/
Copy pathseed256.go
File metadata and controls
178 lines (164 loc) · 6.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package itb
import (
"crypto/rand"
"encoding/binary"
"fmt"
)
// HashFunc256 is the pluggable 256-bit hash function interface.
//
// The function accepts arbitrary-length data and a [4]uint64 seed (256 bits),
// returning a [4]uint64 output. The 256-bit intermediate state enables
// effective key sizes up to 2048 bits through ChainHash256.
//
// PRF-grade hash functions are required (see Definition 2 in SCIENCE.md).
//
// Example wrapper:
//
// // BLAKE3 keyed (256-bit, AVX-512 acceleration)
// func blake3Hash256(data []byte, seed [4]uint64) [4]uint64 {
// var key [32]byte
// binary.LittleEndian.PutUint64(key[0:], seed[0])
// binary.LittleEndian.PutUint64(key[8:], seed[1])
// binary.LittleEndian.PutUint64(key[16:], seed[2])
// binary.LittleEndian.PutUint64(key[24:], seed[3])
// h := blake3.DeriveKey(key, data)
// var out [4]uint64
// for i := range out {
// out[i] = binary.LittleEndian.Uint64(h[i*8:])
// }
// return out
// }
type HashFunc256 func(data []byte, seed [4]uint64) [4]uint64
// Seed256 holds a dynamically-sized symmetric key with a pluggable 256-bit hash function.
//
// Components are consumed 4 per round by ChainHash256, giving 256-bit
// intermediate state. For 2048-bit key (32 components, 8 rounds):
// effective security = 2048 bits.
type Seed256 struct {
Components []uint64
Hash HashFunc256
// BatchHash is the optional 4-way batched counterpart of Hash. When
// non-nil and ITB's runtime detects that both noiseSeed and dataSeed
// of an Encrypt3x256Cfg / Decrypt3x256Cfg invocation expose BatchHash,
// processChunk256 dispatches per-pixel hashing four pixels at a
// time via BatchChainHash256 instead of one pixel per ChainHash256
// call. The Hash field remains the bit-exact reference; BatchHash
// must agree with Hash on every input (see seed256_batch.go for the
// parity invariant). nil disables batched dispatch and preserves
// the legacy single-call code path.
BatchHash BatchHashFunc256
}
// NewSeed256 creates a new 256-bit seed with cryptographically random components.
//
// bits must be a multiple of 256, in range [512, 2048].
// Components count must be a multiple of 4 (4 per ChainHash256 round).
//
// Example:
//
// seed, err := itb.NewSeed256(2048, blake3Hash256)
func NewSeed256(bits int, hashFunc HashFunc256) (*Seed256, error) {
if bits < 512 || bits > MaxKeyBits || bits%256 != 0 {
return nil, fmt.Errorf("itb: seed256 bits must be 512-%d and multiple of 256, got %d", MaxKeyBits, bits)
}
if hashFunc == nil {
return nil, fmt.Errorf("itb: hashFunc must not be nil")
}
n := bits / 64
s := &Seed256{
Components: make([]uint64, n),
Hash: hashFunc,
}
buf := make([]byte, n*8)
if _, err := rand.Read(buf); err != nil {
return nil, fmt.Errorf("itb: crypto/rand: %w", err)
}
for i := 0; i < n; i++ {
s.Components[i] = binary.LittleEndian.Uint64(buf[i*8:])
}
return s, nil
}
// SeedFromComponents256 creates a 256-bit seed from existing uint64 values.
//
// components length must be in range [8, 32] and a multiple of 4.
func SeedFromComponents256(hashFunc HashFunc256, components ...uint64) (*Seed256, error) {
if len(components) < 8 || len(components) > MaxKeyBits/64 {
return nil, fmt.Errorf("itb: components count must be 8-%d, got %d", MaxKeyBits/64, len(components))
}
if len(components)%4 != 0 {
return nil, fmt.Errorf("itb: seed256 components must be multiple of 4, got %d", len(components))
}
if hashFunc == nil {
return nil, fmt.Errorf("itb: hashFunc must not be nil")
}
c := make([]uint64, len(components))
copy(c, components)
return &Seed256{Components: c, Hash: hashFunc}, nil
}
// Bits returns the key size in bits.
func (s *Seed256) Bits() int {
return len(s.Components) * 64
}
// MinPixels returns the minimum pixel count ensuring encoding ambiguity
// exceeds the key space (2^keyBits). Aliases [MinPixelsAuth]'s CCA-
// resistant formula so plain and MAC-authenticated modes share one
// container envelope on small messages.
func (s *Seed256) MinPixels() int {
return s.MinPixelsAuth()
}
// MinPixelsAuth returns the CCA-resistant minimum pixel count. Formula:
// ceil(keyBits / log2(7)).
func (s *Seed256) MinPixelsAuth() int {
return (s.Bits()*minPixelsScale + minPixelsDivisor7 - 1) / minPixelsDivisor7
}
// ChainHash256 computes chained hash across all seed components with 256-bit state.
//
// Each round consumes 4 components and the previous 256-bit output:
//
// h = Hash256(data, [s[0], s[1], s[2], s[3]])
// h = Hash256(data, [s[4]^h[0], s[5]^h[1], s[6]^h[2], s[7]^h[3]])
// ...
func (s *Seed256) ChainHash256(buf []byte) [4]uint64 {
var seed [4]uint64
copy(seed[:], s.Components[0:4])
h := s.Hash(buf, seed)
for i := 4; i < len(s.Components); i += 4 {
seed[0] = s.Components[i] ^ h[0]
seed[1] = s.Components[i+1] ^ h[1]
seed[2] = s.Components[i+2] ^ h[2]
seed[3] = s.Components[i+3] ^ h[3]
h = s.Hash(buf, seed)
}
return h
}
// blockHash256 computes 256-bit hash for a single pixel.
func (s *Seed256) blockHash256(buf []byte, blockIdx int) [4]uint64 {
binary.LittleEndian.PutUint32(buf, uint32(blockIdx))
return s.ChainHash256(buf)
}
// deriveStartPixel computes seed+nonce-dependent pixel offset.
func (s *Seed256) deriveStartPixel(nonce []byte, totalPixels int) int {
buf := make([]byte, 1+len(nonce))
buf[0] = 0x02
copy(buf[1:], nonce)
h := s.ChainHash256(buf)
return int(h[0] % uint64(totalPixels))
}
// deriveInterLockSeed returns the full 256-bit ChainHash output derived
// from the dedicated interlock domain tag (0x04) over the interlock
// nonce. The tag is distinct from the 0x02 tag of
// [Seed256.deriveStartPixel], keeping the two derivations
// cryptographically decorrelated even for byte-identical seed material.
// deriveInterLockSeed exposes the full [4]uint64 for consumers that
// need it as PRF seed material — e.g. the Interlocked Barrier overlay's
// per-chunk keystream.
//
// Called on the dedicated lockSeed slot of the Triple Ouroboros 8-seed
// constellation, keying the 48-bit Interlocked Barrier overlay's
// per-chunk bit-permutation derivation independently of the noiseSeed
// material.
func (s *Seed256) deriveInterLockSeed(nonce []byte) [4]uint64 {
buf := make([]byte, 1+len(nonce))
buf[0] = 0x04
copy(buf[1:], nonce)
return s.ChainHash256(buf)
}