Skip to content

Commit

Permalink
mixing: introduce module
Browse files Browse the repository at this point in the history
This introduces the mixing module and mixpool package which implements
a mixing message pool.  Similar to the transaction mempool, the
mixpool records recently observed mixing messages and allows these
messages to be temporarily stored in memory to be relayed through the
P2P network.  It handles message acceptance, expiry, UTXO ownership
proof checks, and that previously referenced messages have also been
accepted to the mixpool.

The mixpool is also designed with wallet usage in mind, providing most
of these same acceptance rules to mixing messages received by wallets,
and implements queries for messages matching compatible pairings and
ongoing sessions.
  • Loading branch information
jrick committed Aug 28, 2023
1 parent cefb02c commit e970a8a
Show file tree
Hide file tree
Showing 16 changed files with 2,249 additions and 0 deletions.
5 changes: 5 additions & 0 deletions internal/blockchain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ type BlockChain struct {
bulkImportMode bool
}

// ChainParams returns the chain parameters.
func (b *BlockChain) ChainParams() *chaincfg.Params {
return b.chainParams
}

const (
// stakeMajorityCacheKeySize is comprised of the stake version and the
// hash size. The stake version is a little endian uint32, hence we
Expand Down
179 changes: 179 additions & 0 deletions mixing/dcnet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package mixing

import (
"encoding/binary"
"math/big"

"github.com/decred/dcrd/crypto/blake256"
"github.com/decred/dcrd/wire"
)

// SRMixPads creates a vector of exponential DC-net pads from a vector of
// shared secrets with each participating peer in the DC-net.
func SRMixPads(kp [][]byte, my uint32) []*big.Int {
h := blake256.New()
scratch := make([]byte, 8)

pads := make([]*big.Int, len(kp))
partialPad := new(big.Int)
for j := uint32(0); int(j) < len(kp); j++ {
pads[j] = new(big.Int)
for i := uint32(0); int(i) < len(kp); i++ {
if my == i {
continue
}
binary.LittleEndian.PutUint64(scratch, uint64(j)+1)
h.Reset()
h.Write(kp[i])
h.Write(scratch)
digest := h.Sum(nil)
partialPad.SetBytes(digest)
if my > i {
pads[j].Add(pads[j], partialPad)
} else {
pads[j].Sub(pads[j], partialPad)
}
}
pads[j].Mod(pads[j], F)
}
return pads
}

// SRMix creates the padded {m**1, m**2, ..., m**n} message exponentials
// vector. Message must be bounded by the field prime and must be unique to
// every exponential SR run in a mix session to ensure anonymity.
func SRMix(m *big.Int, pads []*big.Int) []*big.Int {
mix := make([]*big.Int, len(pads))
exp := new(big.Int)
for i := int64(0); i < int64(len(mix)); i++ {
mexp := new(big.Int).Exp(m, exp.SetInt64(i+1), nil)
mix[i] = mexp.Add(mexp, pads[i])
mix[i].Mod(mix[i], F)
}
return mix
}

// IntVectorsFromBytes creates a 2-dimensional *big.Int slice from their absolute
// values as bytes.
func IntVectorsFromBytes(vs [][][]byte) [][]*big.Int {
ints := make([][]*big.Int, len(vs))
for i := range vs {
ints[i] = make([]*big.Int, len(vs[i]))
for j := range vs[i] {
ints[i][j] = new(big.Int).SetBytes(vs[i][j])
}
}
return ints
}

// IntVectorsToBytes creates a 2-dimensional slice of big.Int absolute values as
// bytes.
func IntVectorsToBytes(ints [][]*big.Int) [][][]byte {
bytes := make([][][]byte, len(ints))
for i := range ints {
bytes[i] = make([][]byte, len(ints[i]))
for j := range ints[i] {
bytes[i][j] = ints[i][j].Bytes()
}
}
return bytes
}

// AddVectors sums each vector element over F, returning a new vector. When
// peers are honest (DC-mix pads sum to zero) this creates the unpadded vector
// of message power sums.
func AddVectors(vs ...[]*big.Int) []*big.Int {
sums := make([]*big.Int, len(vs))
for i := range sums {
sums[i] = new(big.Int)
for j := range vs {
sums[i].Add(sums[i], vs[j][i])
}
sums[i].Mod(sums[i], F)
}
return sums
}

// Coefficients calculates a{0}..a{n} for the polynomial:
//
// g(x) = a{0} + a{1}x + a{2}x**2 + ... + a{n-1}x**(n-1) + a{n}x**n (mod F)
//
// where
//
// a{n} = -1
// a{n-1} = -(1/1) * a{n}*S{0}
// a{n-2} = -(1/2) * (a{n-1}*S{0} + a{n}*S{1})
// a{n-3} = -(1/3) * (a{n-2}*S{0} + a{n-1}*S{1} + a{n}*S{2})
// ...
//
// The roots of this polynomial are the set of recovered messages.
//
// Note that the returned slice of coefficients is one element larger than the
// slice of partial sums.
func Coefficients(S []*big.Int) []*big.Int {
n := len(S) + 1
a := make([]*big.Int, n)
a[len(a)-1] = big.NewInt(-1)
a[len(a)-1].Add(a[len(a)-1], F) // a{n} = -1 (mod F) = F - 1
scratch := new(big.Int)
for i := 0; i < len(a)-1; i++ {
a[n-2-i] = new(big.Int)
for j := 0; j <= i; j++ {
a[n-2-i].Add(a[n-2-i], scratch.Mul(a[n-1-i+j], S[j]))
}
xinv := scratch.ModInverse(scratch.SetInt64(int64(i)+1), F)
xinv.Neg(xinv)
a[n-2-i].Mul(a[n-2-i], xinv)
a[n-2-i].Mod(a[n-2-i], F)
}
return a
}

// IsRoot checks that the message m is a root of the polynomial with
// coefficients a (mod F) without solving for every root.
func IsRoot(m *big.Int, a []*big.Int) bool {
sum := new(big.Int)
scratch := new(big.Int)
for i := range a {
scratch.Exp(m, scratch.SetInt64(int64(i)), F)
scratch.Mul(scratch, a[i])
sum.Add(sum, scratch)
}
sum.Mod(sum, F)
return sum.Sign() == 0
}

// DCMixPads creates the vector of DC-net pads from shared secrets with each mix
// participant.
func DCMixPads(kp []wire.MixVec, msize, my uint32) Vec {
n := uint32(len(kp))
pads := Vec{
N: n,
Msize: msize,
Data: make([]byte, n*msize),
}
for i := range kp {
if i == int(my) {
continue
}
pads.Xor(&pads, (*Vec)(&kp[i]))
}
return pads
}

// DCMix creates the DC-net vector of message m xor'd into m's reserved
// anonymous slot position of the pads DC-net pads. Panics if len(m) is not the
// vector's message size.
func DCMix(pads *Vec, m []byte, slot uint32) Vec {
dcmix := *pads
dcmix.Data = make([]byte, len(pads.Data))
copy(dcmix.Data, pads.Data)
slotm := dcmix.M(slot)
if len(m) != len(slotm) {
panic("message sizes are not equal")
}
for i := range m {
slotm[i] ^= m[i]
}
return dcmix
}
17 changes: 17 additions & 0 deletions mixing/field.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package mixing

import (
"math/big"
)

// F is the field prime 2**127 - 1.
var F *big.Int

func init() {
F, _ = new(big.Int).SetString("7fffffffffffffffffffffffffffffff", 16)
}

// InField returns whether x is bounded by the field F.
func InField(x *big.Int) bool {
return x.Sign() != -1 && x.Cmp(F) == -1
}
30 changes: 30 additions & 0 deletions mixing/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module github.com/decred/dcrd/mixing

go 1.17

require (
decred.org/cspp/v2 v2.0.1-0.20230307024253-8a22691aa376
github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a
github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/chaincfg/chainhash v1.0.4
github.com/decred/dcrd/chaincfg/v3 v3.2.0
github.com/decred/dcrd/crypto/blake256 v1.0.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
github.com/decred/dcrd/dcrutil/v4 v4.0.1
github.com/decred/dcrd/txscript/v4 v4.1.0
github.com/decred/dcrd/wire v1.6.0
github.com/decred/slog v1.2.0
golang.org/x/crypto v0.7.0
)

require (
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/dchest/siphash v1.2.3 // indirect
github.com/decred/base58 v1.0.5 // indirect
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 // indirect
github.com/decred/dcrd/dcrec v1.0.1 // indirect
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
golang.org/x/sys v0.6.0 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
)
81 changes: 81 additions & 0 deletions mixing/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
decred.org/cspp/v2 v2.0.1-0.20230307024253-8a22691aa376 h1:739v8a7LMXuCTFNodcKYVpfj70CKWvJeE3NKFDn/65I=
decred.org/cspp/v2 v2.0.1-0.20230307024253-8a22691aa376/go.mod h1:+/9jr1RhVshWnc0U/eXxMlxfiu9/f7ia6TTyS0Oh5n0=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/companyzero/sntrup4591761 v0.0.0-20200131011700-2b0d299dbd22/go.mod h1:LoZJNGDWmVPqMEHmeJzj4Weq4Stjc6FKY6FVpY3Hem0=
github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a h1:clYxJ3Os0EQUKDDVU8M0oipllX0EkuFNBfhVQuIfyF0=
github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a/go.mod h1:z/9Ck1EDixEbBbZ2KH2qNHekEmDLTOZ+FyoIPWWSVOI=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
github.com/decred/base58 v1.0.5 h1:hwcieUM3pfPnE/6p3J100zoRfGkQxBulZHo7GZfOqic=
github.com/decred/base58 v1.0.5/go.mod h1:s/8lukEHFA6bUQQb/v3rjUySJ2hu+RioCzLukAVkrfw=
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
github.com/decred/dcrd/chaincfg/chainhash v1.0.4 h1:zRCv6tdncLfLTKYqu7hrXvs7hW+8FO/NvwoFvGsrluU=
github.com/decred/dcrd/chaincfg/chainhash v1.0.4/go.mod h1:hA86XxlBWwHivMvxzXTSD0ZCG/LoYsFdWnCekkTMCqY=
github.com/decred/dcrd/chaincfg/v3 v3.2.0 h1:6WxA92AGBkycEuWvxtZMvA76FbzbkDRoK8OGbsR2muk=
github.com/decred/dcrd/chaincfg/v3 v3.2.0/go.mod h1:2rHW1TKyFmwZTVBLoU/Cmf0oxcpBjUEegbSlBfrsriI=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 h1:TvGTmUBHDU75OHro9ojPLK+Yv7gDl2hnUvRocRCjsys=
github.com/decred/dcrd/crypto/ripemd160 v1.0.2/go.mod h1:uGfjDyePSpa75cSQLzNdVmWlbQMBuiJkvXw/MNKRY4M=
github.com/decred/dcrd/dcrec v1.0.1 h1:gDzlndw0zYxM5BlaV17d7ZJV6vhRe9njPBFeg4Db2UY=
github.com/decred/dcrd/dcrec v1.0.1/go.mod h1:CO+EJd8eHFb8WHa84C7ZBkXsNUIywaTHb+UAuI5uo6o=
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3 h1:l/lhv2aJCUignzls81+wvga0TFlyoZx8QxRMQgXpZik=
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3/go.mod h1:AKpV6+wZ2MfPRJnTbQ6NPgWrKzbe9RCIlCF/FKzMtM8=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/dcrutil/v4 v4.0.1 h1:E+d2TNbpOj0f1L9RqkZkEm1QolFjajvkzxWC5WOPf1s=
github.com/decred/dcrd/dcrutil/v4 v4.0.1/go.mod h1:7EXyHYj8FEqY+WzMuRkF0nh32ueLqhutZDoW4eQ+KRc=
github.com/decred/dcrd/txscript/v4 v4.1.0 h1:uEdcibIOl6BuWj3AqmXZ9xIK/qbo6lHY9aNk29FtkrU=
github.com/decred/dcrd/txscript/v4 v4.1.0/go.mod h1:OVguPtPc4YMkgssxzP8B6XEMf/J3MB6S1JKpxgGQqi0=
github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w=
github.com/decred/dcrd/wire v1.6.0 h1:YOGwPHk4nzGr6OIwUGb8crJYWDiVLpuMxfDBCCF7s/o=
github.com/decred/dcrd/wire v1.6.0/go.mod h1:XQ8Xv/pN/3xaDcb7sH8FBLS9cdgVctT7HpBKKGsIACk=
github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM=
github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jrick/wsrpc/v2 v2.3.4/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
53 changes: 53 additions & 0 deletions mixing/internal/chacha20prng/prng.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) 2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package chacha20prng

import (
"encoding/binary"
"strconv"

"golang.org/x/crypto/chacha20"
)

// SeedSize is the required length of seeds for New.
const SeedSize = 32

// Reader is a ChaCha20 PRNG for a DC-net run. It implements io.Reader.
type Reader struct {
cipher *chacha20.Cipher
}

// New creates a ChaCha20 PRNG seeded by a 32-byte key and a run iteration. The
// returned reader is not safe for concurrent access. This will panic if the
// length of seed is not SeedSize bytes.
func New(seed []byte, run uint32) *Reader {
if l := len(seed); l != SeedSize {
panic("chacha20prng: bad seed length " + strconv.Itoa(l))
}

nonce := make([]byte, chacha20.NonceSize)
binary.LittleEndian.PutUint32(nonce[:4], run)

cipher, _ := chacha20.NewUnauthenticatedCipher(seed, nonce)
return &Reader{cipher: cipher}
}

// Read implements io.Reader.
func (r *Reader) Read(b []byte) (int, error) {
// Zero the source such that the destination is written with just the
// keystream. Destination and source are allowed to overlap (exactly).
for i := range b {
b[i] = 0
}
r.cipher.XORKeyStream(b, b)
return len(b), nil
}

// Next returns the next n bytes from the reader.
func (r *Reader) Next(n int) []byte {
b := make([]byte, n)
r.cipher.XORKeyStream(b, b)
return b
}
Loading

0 comments on commit e970a8a

Please sign in to comment.