diff --git a/.gitignore b/.gitignore index bfebe8845..196619658 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ *.dylib *.test *.out -.experimental/ \ No newline at end of file +.experimental/ +examples/mam-transmit/transmit.sh +examples/mam-receive/receive.sh diff --git a/examples/mam-receive/main.go b/examples/mam-receive/main.go new file mode 100644 index 000000000..8b0e0db00 --- /dev/null +++ b/examples/mam-receive/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "flag" + "fmt" + "log" + "time" + + "github.com/simia-tech/env" + + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/mam/v1" +) + +var ( + endpointURL = env.String("ENDPOINT_URL", "https://nodes.thetangle.org:443") + mode = env.String("MODE", "public", env.AllowedValues("public", "private", "restricted")) + sideKey = env.String("SIDE_KEY", "") +) + +func main() { + follow := flag.Bool("f", false, "don't exit and check every second for new messages") + flag.Parse() + root := flag.Arg(0) + + cm, err := mam.ParseChannelMode(mode.Get()) + if err != nil { + log.Fatal(err) + } + + api, err := api.ComposeAPI(api.HTTPClientSettings{ + URI: endpointURL.Get(), + }) + if err != nil { + log.Fatal(err) + } + + receiver := mam.NewReceiver(api) + if err := receiver.SetMode(cm, sideKey.Get()); err != nil { + log.Fatal(err) + } + + fmt.Printf("receive root %q from %s channel...\n", root, cm) +loop: + nextRoot, messages, err := receiver.Receive(root) + if err != nil { + log.Fatal(err) + } + for _, message := range messages { + fmt.Println(message) + } + if *follow { + time.Sleep(time.Second) + if len(messages) > 0 { + root = nextRoot + } + goto loop + } + if len(messages) == 0 { + fmt.Println("no messages found") + } +} diff --git a/examples/mam-transmit/main.go b/examples/mam-transmit/main.go new file mode 100644 index 000000000..8a90d0f9f --- /dev/null +++ b/examples/mam-transmit/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "flag" + "fmt" + "log" + + "github.com/simia-tech/env" + + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/consts" + "github.com/iotaledger/iota.go/mam/v1" + "github.com/iotaledger/iota.go/pow" +) + +var ( + endpointURL = env.String("ENDPOINT_URL", "https://nodes.thetangle.org:443") + seed = env.String("SEED", "") + mwm = env.Int("MWM", 9) + mode = env.String("MODE", "public", env.AllowedValues("public", "private", "restricted")) + sideKey = env.String("SIDE_KEY", "") +) + +func main() { + flag.Parse() + messages := flag.Args() + + cm, err := mam.ParseChannelMode(mode.Get()) + if err != nil { + log.Fatal(err) + } + + _, powFunc := pow.GetFastestProofOfWorkImpl() + + api, err := api.ComposeAPI(api.HTTPClientSettings{ + URI: endpointURL.Get(), + LocalProofOfWorkFunc: powFunc, + }) + if err != nil { + log.Fatal(err) + } + + transmitter := mam.NewTransmitter(api, seed.Get(), uint64(mwm.Get()), consts.SecurityLevelMedium) + if err := transmitter.SetMode(cm, sideKey.Get()); err != nil { + log.Fatal(err) + } + + for _, message := range messages { + fmt.Printf("transmit message %q to %s channel...\n", message, cm) + root, err := transmitter.Transmit(message) + if err != nil { + log.Fatal(err) + } + fmt.Printf("transmitted to root %q\n", root) + } +} diff --git a/go.mod b/go.mod index 913ee7fe8..2b18647e5 100644 --- a/go.mod +++ b/go.mod @@ -4,21 +4,24 @@ go 1.12 require ( github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 // indirect - github.com/apsdehal/go-logger v0.0.0-20190506062552-f85330a4b532 // indirect github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 github.com/dgraph-io/badger v1.5.4 github.com/dgryski/go-farm v0.0.0-20190323231341-8198c7b169ec // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/golang/snappy v0.0.1 // indirect + github.com/google/go-cmp v0.2.0 // indirect github.com/onsi/ginkgo v1.8.0 github.com/onsi/gomega v1.5.0 github.com/pkg/errors v0.8.1 + github.com/simia-tech/env v0.1.0 github.com/stretchr/testify v1.3.0 // indirect + github.com/tidwall/pretty v1.0.0 // indirect github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect github.com/xdg/stringprep v1.0.0 // indirect go.mongodb.org/mongo-driver v1.0.0 golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 // indirect - golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 // indirect + golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect + golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect gopkg.in/h2non/gock.v1 v1.0.14 ) diff --git a/go.sum b/go.sum index 436a13491..a4a751fa1 100644 --- a/go.sum +++ b/go.sum @@ -2,18 +2,19 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkBy github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/apsdehal/go-logger v0.0.0-20190506062552-f85330a4b532 h1:xTYdKP4GbVSANiKn8bdlBzQp08Nq8I1wlB49Dunh0QA= -github.com/apsdehal/go-logger v0.0.0-20190506062552-f85330a4b532/go.mod h1:U3/8D6R9+bVpX0ORZjV+3mU9pQ86m7h1lESgJbXNvXA= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dgraph-io/badger v1.5.4 h1:gVTrpUTbbr/T24uvoCaqY2KSHfNLVGm0w+hbee2HMeg= github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgryski/go-farm v0.0.0-20190323231341-8198c7b169ec h1:sElGDs3V8VdCxH5tWi0ycWJzteOPLJ3HtItSSKI95PY= github.com/dgryski/go-farm v0.0.0-20190323231341-8198c7b169ec/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -21,6 +22,8 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -36,29 +39,39 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/simia-tech/env v0.1.0 h1:QTq3uAvWT0HUMgH6VWQmOOdb81tjizFKNmaQNOuPu90= +github.com/simia-tech/env v0.1.0/go.mod h1:eVRQ7W5NXXHifpPAcTJ3r5EmoGgMn++dXfSVbZv3Opo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= go.mongodb.org/mongo-driver v1.0.0 h1:KxPRDyfB2xXnDE2My8acoOWBQkfv3tz0SaWTRZjJR0c= go.mongodb.org/mongo-driver v1.0.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/mam/v1/address.go b/mam/v1/address.go new file mode 100644 index 000000000..50d83f5cd --- /dev/null +++ b/mam/v1/address.go @@ -0,0 +1,47 @@ +package mam + +import ( + "github.com/iotaledger/iota.go/address" + "github.com/iotaledger/iota.go/consts" + "github.com/iotaledger/iota.go/curl" + "github.com/iotaledger/iota.go/trinary" +) + +func makeAddress(mode ChannelMode, root trinary.Trits, sideKey trinary.Trytes) (trinary.Trytes, error) { + if mode == ChannelModePublic { + return toAddress(root) + } + + sideKeyTrits, err := trinary.TrytesToTrits(sideKey) + if err != nil { + return "", err + } + + h := curl.NewCurlP81() + if err := h.Absorb(sideKeyTrits); err != nil { + return "", err + } + if err := h.Absorb(root); err != nil { + return "", err + } + hashedRoot, err := h.Squeeze(consts.HashTrinarySize) + if err != nil { + return "", err + } + + return toAddress(hashedRoot) +} + +func toAddress(root trinary.Trits) (trinary.Trytes, error) { + rootTrytes, err := trinary.TritsToTrytes(root) + if err != nil { + return "", err + } + + chkSum, err := address.Checksum(rootTrytes) + if err != nil { + return "", err + } + + return rootTrytes + chkSum, nil +} diff --git a/mam/v1/api.go b/mam/v1/api.go new file mode 100644 index 000000000..58b7e8cce --- /dev/null +++ b/mam/v1/api.go @@ -0,0 +1,15 @@ +package mam + +import ( + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/bundle" + "github.com/iotaledger/iota.go/transaction" + "github.com/iotaledger/iota.go/trinary" +) + +// API defines an interface with a subset of methods of `api.API`. +type API interface { + PrepareTransfers(seed trinary.Trytes, transfers bundle.Transfers, opts api.PrepareTransfersOptions) ([]trinary.Trytes, error) + SendTrytes(trytes []trinary.Trytes, depth uint64, mwm uint64, reference ...trinary.Hash) (bundle.Bundle, error) + FindTransactionObjects(query api.FindTransactionsQuery) (transaction.Transactions, error) +} diff --git a/mam/v1/api_test.go b/mam/v1/api_test.go new file mode 100644 index 000000000..9c1458036 --- /dev/null +++ b/mam/v1/api_test.go @@ -0,0 +1,56 @@ +package mam_test + +import ( + "github.com/pkg/errors" + + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/bundle" + "github.com/iotaledger/iota.go/transaction" + "github.com/iotaledger/iota.go/trinary" +) + +type fakeAPI struct { + prepareTransfers func(trinary.Trytes, bundle.Transfers, api.PrepareTransfersOptions) ([]trinary.Trytes, error) + sendTrytes func([]trinary.Trytes, uint64, uint64, ...trinary.Hash) (bundle.Bundle, error) + findTransactionObjects func(api.FindTransactionsQuery) (transaction.Transactions, error) +} + +func newFakeAPI() *fakeAPI { + return &fakeAPI{ + prepareTransfers: func(_ trinary.Trytes, _ bundle.Transfers, _ api.PrepareTransfersOptions) ([]trinary.Trytes, error) { + return nil, errors.New("not implemented") + }, + sendTrytes: func(_ []trinary.Trytes, _ uint64, _ uint64, _ ...trinary.Hash) (bundle.Bundle, error) { + return nil, errors.New("not implemented") + }, + findTransactionObjects: func(_ api.FindTransactionsQuery) (transaction.Transactions, error) { + return nil, errors.New("not implemented") + }, + } +} + +func (f *fakeAPI) PrepareTransfers(seed trinary.Trytes, transfers bundle.Transfers, opts api.PrepareTransfersOptions) ([]trinary.Trytes, error) { + return f.prepareTransfers(seed, transfers, opts) +} + +func (f *fakeAPI) SendTrytes(trytes []trinary.Trytes, depth uint64, mwm uint64, reference ...trinary.Hash) (bundle.Bundle, error) { + return f.sendTrytes(trytes, depth, mwm, reference...) +} + +func (f *fakeAPI) FindTransactionObjects(query api.FindTransactionsQuery) (transaction.Transactions, error) { + return f.findTransactionObjects(query) +} + +func err(returns []interface{}) error { + if len(returns) == 0 { + return nil + } + last := returns[len(returns)-1] + if last == nil { + return nil + } + if e, ok := last.(error); ok { + return e + } + return nil +} diff --git a/mam/v1/channel.go b/mam/v1/channel.go new file mode 100644 index 000000000..a4c6cce35 --- /dev/null +++ b/mam/v1/channel.go @@ -0,0 +1,42 @@ +package mam + +import ( + "github.com/iotaledger/iota.go/consts" + "github.com/iotaledger/iota.go/trinary" +) + +type channel struct { + mode ChannelMode + sideKey trinary.Trytes + nextRoot trinary.Trits + securityLevel consts.SecurityLevel + start uint64 + count uint64 + nextCount uint64 + index uint64 +} + +func newChannel(securityLevel consts.SecurityLevel) *channel { + return &channel{ + mode: ChannelModePublic, + sideKey: consts.NullHashTrytes, + securityLevel: securityLevel, + start: 0, + count: 1, + nextCount: 1, + index: 0, + } +} + +func (c *channel) incIndex() { + if c.index == c.count-1 { + c.start += c.nextCount + c.index = 0 + } else { + c.index++ + } +} + +func (c *channel) nextStart() uint64 { + return c.start + c.count +} diff --git a/mam/v1/channel_mode.go b/mam/v1/channel_mode.go new file mode 100644 index 000000000..18c7f4114 --- /dev/null +++ b/mam/v1/channel_mode.go @@ -0,0 +1,36 @@ +package mam + +import ( + "strings" + + "github.com/pkg/errors" +) + +// Error definitions +var ( + ErrCouldNotParseChannelMode = errors.New("could not parse channel mode") +) + +// ChannelMode is an enum of channel modes. +type ChannelMode string + +// Definition of possible channel modes. +const ( + ChannelModePublic ChannelMode = "public" + ChannelModePrivate ChannelMode = "private" + ChannelModeRestricted ChannelMode = "restricted" +) + +// ParseChannelMode parses a channel mode from the given string. +func ParseChannelMode(input string) (ChannelMode, error) { + switch cm := strings.TrimSpace(strings.ToLower(input)); cm { + case "public": + return ChannelModePublic, nil + case "private": + return ChannelModePrivate, nil + case "restricted": + return ChannelModeRestricted, nil + default: + return "", errors.Wrapf(ErrCouldNotParseChannelMode, "input %q", cm) + } +} diff --git a/mam/v1/mam_test.go b/mam/v1/mam_test.go index 12a1e69e0..3832fdf0a 100644 --- a/mam/v1/mam_test.go +++ b/mam/v1/mam_test.go @@ -1,15 +1,15 @@ package mam_test import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/iotaledger/iota.go/consts" "github.com/iotaledger/iota.go/converter" . "github.com/iotaledger/iota.go/curl" . "github.com/iotaledger/iota.go/mam/v1" . "github.com/iotaledger/iota.go/merkle" "github.com/iotaledger/iota.go/trinary" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" ) const ( @@ -19,9 +19,10 @@ const ( message = "{\"message\":\"Message from Alice\",\"timestamp\":\"2019-4-8 22:41:01\"}" ) -var _ = Describe("Mam", func() { +var _ = Describe("MAMCreate", func() { + + Context("MAMCreate and MAMParse", func() { - Context("Mam()", func() { It("Mam", func() { var index uint64 = 7 var start uint64 = 0 @@ -57,5 +58,6 @@ var _ = Describe("Mam", func() { } } }) + }) }) diff --git a/mam/v1/receiver.go b/mam/v1/receiver.go new file mode 100644 index 000000000..d59ed7779 --- /dev/null +++ b/mam/v1/receiver.go @@ -0,0 +1,147 @@ +package mam + +import ( + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/consts" + "github.com/iotaledger/iota.go/converter" + "github.com/iotaledger/iota.go/transaction" + "github.com/iotaledger/iota.go/trinary" +) + +// Receiver implements a receiver for MAM-Messages. +type Receiver struct { + api API + mode ChannelMode + sideKey trinary.Trytes +} + +// NewReceiver returns a new receiver. +func NewReceiver(api API) *Receiver { + return &Receiver{ + api: api, + mode: ChannelModePublic, + sideKey: consts.NullHashTrytes, + } +} + +// SetMode sets the channel mode. +func (r *Receiver) SetMode(m ChannelMode, sideKey trinary.Trytes) error { + if m != ChannelModePublic && m != ChannelModePrivate && m != ChannelModeRestricted { + return ErrUnknownChannelMode + } + if m == ChannelModeRestricted { + if sideKey == "" { + return ErrNoSideKey + } + r.sideKey = sideKey + } + r.mode = m + return nil +} + +// Mode returns the channel mode. +func (r *Receiver) Mode() ChannelMode { + return r.mode +} + +// SideKey returns the channel's side key. +func (r *Receiver) SideKey() trinary.Trytes { + return r.sideKey +} + +// Receive tries to receive all messages from the specified root and returns them along with the next root. +func (r *Receiver) Receive(root trinary.Trytes) (trinary.Trytes, []string, error) { + rootTrits, err := trinary.TrytesToTrits(root) + if err != nil { + return "", nil, err + } + + address, err := makeAddress(r.mode, rootTrits, r.sideKey) + if err != nil { + return "", nil, err + } + + txs, err := r.api.FindTransactionObjects(api.FindTransactionsQuery{Addresses: trinary.Hashes{address}}) + if err != nil { + return "", nil, err + } + + bundles := map[trinary.Trytes][]transaction.Transaction{} + for _, tx := range txs { + if v, ok := bundles[tx.Bundle]; ok { + bundles[tx.Bundle] = append(v, tx) + } else { + bundles[tx.Bundle] = []transaction.Transaction{tx} + } + } + + var nextRoot trinary.Trytes + messages := []string{} + for _, bundle := range bundles { + candidateTx := findHeadTx(bundle) + if candidateTx == nil { + continue + } + + tx := candidateTx + message := tx.SignatureMessageFragment + for tx != nil && tx.CurrentIndex != tx.LastIndex { + tx = findTxByHash(bundle, tx.TrunkTransaction) + if tx != nil { + message += tx.SignatureMessageFragment + } + } + if tx == nil { + continue + } + + if tx.CurrentIndex == tx.LastIndex { + nr, message, err := r.decodeMessage(rootTrits, message) + if err != nil { + return "", nil, err + } + messages = append(messages, message) + nextRoot = nr + } + } + + return nextRoot, messages, nil +} + +func (r *Receiver) decodeMessage(root trinary.Trits, encodedMessage trinary.Trytes) (trinary.Trytes, string, error) { + messageTrits, err := trinary.TrytesToTrits(encodedMessage) + if err != nil { + return "", "", err + } + messageLength := uint64(len(messageTrits)) + + _, nextRoot, messageTrytes, _, err := MAMParse(messageTrits, messageLength, r.sideKey, root) + if err != nil { + return "", "", err + } + + message, err := converter.TrytesToASCII(messageTrytes) + if err != nil { + return "", "", err + } + + return nextRoot, message, nil +} + +func findHeadTx(txs []transaction.Transaction) *transaction.Transaction { + for _, tx := range txs { + if tx.CurrentIndex == 0 { + return &tx + } + } + return nil +} + +func findTxByHash(txs []transaction.Transaction, hash trinary.Trytes) *transaction.Transaction { + for _, tx := range txs { + if tx.Hash == hash { + return &tx + } + } + return nil +} diff --git a/mam/v1/receiver_test.go b/mam/v1/receiver_test.go new file mode 100644 index 000000000..73b524a9c --- /dev/null +++ b/mam/v1/receiver_test.go @@ -0,0 +1,178 @@ +package mam_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/iotaledger/iota.go/api" + mam "github.com/iotaledger/iota.go/mam/v1" + "github.com/iotaledger/iota.go/transaction" + "github.com/iotaledger/iota.go/trinary" +) + +var _ = Describe("Receiver", func() { + + Context("Receive", func() { + + It("Should receive a message from a public channel", func() { + fakeAPI := newFakeAPI() + fakeAPI.findTransactionObjects = func(query api.FindTransactionsQuery) (transaction.Transactions, error) { + Expect(query).To(Equal(api.FindTransactionsQuery{ + Addresses: trinary.Hashes{"PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTXUMUPGBBN9UMUPGBBN9"}, + })) + return transaction.Transactions{ + transaction.Transaction{ + Hash: "YJLLUHLGJNZNMT9JGIHNOTPUNHHZWSOC9NCUFYYUNX9VNVMSWIDYAFBYTBBCVMJUOLDZESRWTVZX99999", + SignatureMessageFragment: "HH9ZURAASMZCC9EESAHNIFQJRUEPETKZCWWYDHXHUYZOUXCUI99TUATEHWVPIZQYHWYOBNSQWZKKUVRZQHTPJEPAJFCBWOXRZMWUACSUUIADMCMV9ZYYNAYLDZMFSHSAVVXNQOCZDOGMQVCJLEBGAYNGYJKOPDHBVDKBGVVCSQIBAHVFJOHIKHQKSDMTCVHIAQUB9ULLSSUUUVKI9X9AYY9FBRWZDONSKVFQRIEIYMFFWVHUT9EZMDVSDHNTWAIKEWZFUARBPFJZWEBEYRDRNSSHOQWJAWTSYTFMYLDGTTXMWCMALHJFBAYIESEBNBEZCZQJGZPAHDADUULD9UDRXCDPOFIPKBLL9RUPPIMH9MEJSUCWGQMKBVFTAZSOFPUQHXUQBJCACNHCHGJYVGHJPUKSMZQWVSPBTQOGTALQUQXUCVAXRBKRSJZPXQIWBRIIEOC9JXVTBNEBAOZBOOOPPTNKMYJCDEFJJQXAIOIBLCSPFFSDGVXYKNUPWMVOEMCJXMVLYSFFRBUAAYXROCEYIER9SBBGJRJRNBOBKAZWGSEY9FK9GJBFZY9LBBT9GXIAKKCQEBZASGUSDFDUC9QYSTPAXPLLEUSKIMJDOVCAFYKSP9WGB9A9YDAQCDNTHJJQWCZHIQTQYKPBXMTWNSBKZJESTVURV9PKJFAEEUCVFXXMNBMOE9GTYNJV9TUJKPQEXOIEN9NEZKWXXDZBTIGXPNBHMNQGEJ9HOFGHUAAVHXO9UBNZOQFKFRDTIOKBCSPYPZ9QEATBLCHXXPNPEXTYGZHDRSYVCMBGK9XQBSECRXJSKD9QFFMVAFDAIYWICUSDKKTKMPWHCHMMNHDTGXYBLTOQTUMQLPFLFGLWEALWFCFEYXQKOBTVH9GIAFHLNQDPIZOAPMVRFWDQDQKMNDJVPCNL9IVRDMEZSVUSGDCH9IFUKQZCJUEMQMQFGTLWTUBGHBYLKKWJFWUVTVLMQALCIPTJUEMRXAGGXSHJQCYZXDNTFL9FHYN9JTUTWJBJWMKVPDXFIMOOBBRUYP9SZZHNKOHCWY9DNQWZKDYTMPCD9OKRCQXTZKIIMGXNBKAAQVBHXULWRGCEVOSPFJVKGIQBGUUVEVHTCZBQOFOIERIYCDKZ9JCNPUUQPAQEM9UXOMBICNYBNXIKTIWVNNUNJCBOVOXV9YCYQUPJJGRCOUVKSELOQECMOACRCHHTSFWWQGBZT9QYTQPDTXLVVPQXADFDR9DMEPIVIUTBJYBDVROEIEVPTBLIZWI9DEVCNLTWYILRQTRKYNAHSEJJMZLIAOF9JSWPHYSWSRYDDUMMTKIACLFJGQUXKFFFXHGHXNTLFHUKHMW9REHQSQGZHMMXMIVAPOPM9IULJPMBFTHOJIZDECOFFALTSJXEVBHNPCMWWJWOVOOPQHHBGMMXGKAMMOLHHJVYCCSQMBWMCXPRSIKLEFZPLXTEUPITXCQ9AUMTAXORLWUJZBZFCYHAHBDKMMCUVQZ9VUJQCZSBRPEJLWNTGLKYRGTOEMVENSRJWPBVYNNWBZBMXTXSYXKIOCPYOKVNCVIIHFIWHJPYVAOYBW9SCFLLUMQACVYYTWZK9SBPCPAJTRRJSBDCXSBSHTHDEKKXWKYBH9TCQGJOQRKXMFVDFOFNXPMVB9XVYSJURFGPMOVADOCQXXRKOOZYLKQEDKCKAKGLICDPGCVZV9TQBIHJFPKFVO9VQDAWTKSPFSHJW9D9VRGIQPGJNFNMGVXBOOUKYCCJNW9VSFLYHLJIDDXEBKXYLQNROYQSPIZBXETKOPJXVEWCVWZTVEZMQJYTYPYEAQTQYOXVNKRFGOZXZQIKOBQSUHVMXRKXSEHPIAZPYGNZXXWB9COQV9MVLSXZJICFLEOVLZFWIA9JUIPFIOHXFXVBYXOHKY9RWMEVWCYRDHBEOZ9MAPOFT9WNEFBBURIQUN9RUSSM9FQFUSNOPTZLJIPYV9SDCO9EJRMQIM9JPRYCEUTM9FOXNRIZTGUP9XKXLGA9CNYQEGKOQOAZQEPIMDWL9XAPCIBAVMMZBLIQEWFWWULEPXIMPBJORAHGSMCHDNSQMMWVKGCGRLFLA9ZHHMJVCNNSNETRIBQTPDB9MX9VSNSEOQIK9SZDITUMMESYKJYD9YKZKCOYIUHLYOZFWJEJUACE9STXQB9RHLL9YVNITZIULZYUJNH", + Address: "PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTX", + CurrentIndex: 0x1, + LastIndex: 0x2, + Bundle: "FGEYEVYLIYESSZTIBXAIDQSK9QSJSX9JFLYKHETYIQRCWVRBVWIDQJBEUSHOIKRLJXLPSGWALTKFXSVCW", + TrunkTransaction: "VSAASONOCUUTNIVXUJ99ES9ZPPWVZOZBAIHL9ALHYDLYNFXSDYIOVUFHBZKVR9ETZDZHI9YHZZYO99999", + BranchTransaction: "CTHMUYWJYAICTJDPVKVIHDBLYDUKVVQPWKXAUW9FATKLSRUVSRTYAHYMTXYNJSZZSAOEXSXSYOLTZ9999", + Nonce: "POWSRVIO9LVB999LNVMMGPTNNNV", + }, + transaction.Transaction{ + Hash: "VSAASONOCUUTNIVXUJ99ES9ZPPWVZOZBAIHL9ALHYDLYNFXSDYIOVUFHBZKVR9ETZDZHI9YHZZYO99999", + SignatureMessageFragment: "RDYUTFNZSKEHVWXFYVXUWYPBEUFULLNQRYYRHJRYNRMPGSIVRDPIAYIVKZNLECLRPVCOLPCVLTSGHCDSYUEYAV9QPLHO9VJ9AZQRNZQSJBNZCIMMBAAGB9YXKINSN9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", + Address: "PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTX", + CurrentIndex: 0x2, + LastIndex: 0x2, + Bundle: "FGEYEVYLIYESSZTIBXAIDQSK9QSJSX9JFLYKHETYIQRCWVRBVWIDQJBEUSHOIKRLJXLPSGWALTKFXSVCW", + TrunkTransaction: "CTHMUYWJYAICTJDPVKVIHDBLYDUKVVQPWKXAUW9FATKLSRUVSRTYAHYMTXYNJSZZSAOEXSXSYOLTZ9999", + BranchTransaction: "BZWSJSIGOCGELCBNELWXHQKNXZVHWSPFMIMI9CMBEZITWJNVNNCOWNSVQBFLIUILBQSUUCUIFGPW99999", + Nonce: "POWSRVIO9LVB999UNTTPPPVNNNV", + }, + transaction.Transaction{ + Hash: "FPZSZEPOSNZVCMDNIJVBNRJHMUXBKRIFEHBERVQCRJXJCIAXGEUHFJKKAZTCDBNWCFPZYUADQDPA99999", + SignatureMessageFragment: "AZBCBCDTA9YWALKBOBQCBHBEOGVFTJGQGFVH9DDBETQCYZLHSBQQOEXKHVCRLBOTABTUYEJDNHXOBLWNITRDDKYCJOJVSLUDPBOYJ9WMLNDFWXKDAAJZDAIUUMLPVQMWIYJYE9G9BEIHBOQBKZZSLXBKGPDLPYRFLJPUTJOAJZELNGZFQBEPP9KNFNBQCQIBANQFLOQLQMFNMKDYJITRVLPDCWEBENYVT9GVPELRCMOHXPTCJMEEQLQGM9QG9WKHWJTBGXBVALITU9WIECZMQJLADX9WPIFB9QWLKBTEL9FTKLAQENDRESAZNSYGWCDMKO9GKVED9SXZKOJONWWIPKCTCCFBWYSCFAJPIQOCCCPLXZSNQJ9APQPYQMHQZBICSTJQIIHOSJKCZBFTMHEKPHOVYUXTBGOV99BRHKVFXOJO9QUCFKAUCZCSIFHNBRUNWSXOQAHOYHKHXLBRDSWHMPFEXLVEZWGLNAJOQSANSEJM9UOORSAWTNWIUPM9OJYMYHZDCF9LQYYYYCPQ9MAU9ABZPLLDTPISQBKIZMSOURGLKSQV9AXPFOBIPUNBQGZOMUGLJYBDXXWRJYOIXQTRVQYYOZAJ9YOFIVFBMRABSJFVNNMTMPYIEGNNPQEAPDXYYIIRNYVYUN9KAICLDLEDMGM9MKMAOMKNJTFJFXW9KRDELIPXMTSQFKHVMBP9VOS9SIFTCTHFQHNLAABDYOQDRUSI9MFUVTCWZB9CWRVZZQASQROBORXB9K9RXYOQNTPC9CNMHRISBQFSGNBGAOCYEHSVGV9PG99CUGFPTINEBWFUXUFICLWSVFGQVEJIUAWXGPXXXSTGQI9XHETTXFGYETDNUSVSAPNDOBVNWOG9AANDIIOKCWGF9NQYPMEWQPZGPHFIZQPV9BJGHSZGJFMXGJBMSVWFZYCSEYOPGCNJOZPSNHHYUVUMCHWYDJJDGNRJZXDTLWSHZVBVN9BLQKRAKKGQAYVPODVJDSUJRZHKKKPXVURBAACAUFUANUSCVFIYAUFDRHGHVNNBZVIVLJWNJTILTHGJM9UJSYBBZKOIBQXFRGEELNYLKGXWCUGXWL9XANUK9ARKCDRXXGTPN9OIUUSLJQJMYHMVVXMRMBXIAAZMRHZOPYOLLUAZCFRJSLRMXHPLGJCWOCPXRUIRXXHMKWQIQNZMANMAAJKDOLOLNKGESNAGKR9DCSJVWJWXKD9C9GHCKYRZWMPZUOBUCOAHAFINTREHCTZJCOVRDIBOFXMHHODGWSYBOQHQNG9PEOHUZXJRAKAOOSBNOJJAFFVNHLDKBVZIXNNINO9UDVMSEIROQBHYLLRKRMGAC9ORPOHABXJNXBHTIRMOPDLGSEBCNVEGCJIYZ9TFPHYAKICGBOMIHHWUAPFYMNBBRJNLSDWOVUBOIDJXXULJ99EHPYMLCDJI9NCORXTV9APH9YJSTOHUE9ENKFRKTSXT9ALCZNRYVHFYGSAHIKGURPCYPIUNGVRMDCUXXQXQOBRJRKQRQYMCDBCD9RXBFNBUARVRFTZLXZZXWWAFJKVBRGQDUUBDYUDSUXYOBPJMYTWYWPENKZOYMW9PALW9PCJZIXXUJ9RRVKDSPU9AGVPIZNWVYOECDOGZSTYCNRIGBKYVKRYFQJISNBNJOSEYED9HFPYJY9LTSIDYEUAVTUWFYGUHUNDPPVSPVZXPWUPHWRPULJYLODC9HXTZVJESQ9GQG9B9KTUYHIAGXVXOCZLUJBSKWTNYHGRAPTINBUINDHAVOINYETKGQ9OPMHZUQJFGWMPT9TWKHBMDJAWBWFIVSRBOLEZOYMLEDEDGWSKKIKNCYWFUVTFLUHEDHIOQVJVXXLQXETUSZJLUZA9BVTJTTMKSSXBPZLEAZ99XMRJBOHTMGUQSUXEFNKOKXBBNDMEUIBXG9ERVNSYFYFBGLJGIPPLQTVMVHBEMFNXLREVZEPHKKCAJNQ9AYPIXYUUUFACDEAJPHJYEQOBHUKCYTNLVVYQAKJBGH9UCSNALCEOGNBURFBTVFCNQHJ9GVBWQWMGPCQPEIKPSLLD9XXDC9XOZLPYINCYDJ9WPLBQADOI9GLAVGORRCPZDLTFKQLUDSRMFUHAXEVCVPBXQNWANKXDFKFRJ9GNIGATVAAMOFODQDBYMAKUXFOPZFAMXVAQXULGYLMTGHMONDZAXGAGYLSG", + Address: "PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTX", + CurrentIndex: 0x0, + LastIndex: 0x2, + Bundle: "FGEYEVYLIYESSZTIBXAIDQSK9QSJSX9JFLYKHETYIQRCWVRBVWIDQJBEUSHOIKRLJXLPSGWALTKFXSVCW", + TrunkTransaction: "YJLLUHLGJNZNMT9JGIHNOTPUNHHZWSOC9NCUFYYUNX9VNVMSWIDYAFBYTBBCVMJUOLDZESRWTVZX99999", + BranchTransaction: "CTHMUYWJYAICTJDPVKVIHDBLYDUKVVQPWKXAUW9FATKLSRUVSRTYAHYMTXYNJSZZSAOEXSXSYOLTZ9999", + Nonce: "POWSRVIO9LVB999OEKKKKVTNNNT", + }, + }, nil + } + receiver := mam.NewReceiver(fakeAPI) + + err := receiver.SetMode(mam.ChannelModePublic, "") + Expect(err).NotTo(HaveOccurred()) + + nextRoot, messages, err := receiver.Receive("PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTXUMUPGBBN9") + Expect(err).NotTo(HaveOccurred()) + Expect(messages).To(Equal([]string{"Hello!"})) + Expect(nextRoot).To(Equal("HTGAUZTBH9SIVJVREIXLGAPPVQOFZZCRWDYSDG9JLXLFREYRWBEOBPSCSFTV9XZNIVZNAZINBAFDEWUZ9")) + }) + + It("Should receive a message from a private channel", func() { + fakeAPI := newFakeAPI() + fakeAPI.findTransactionObjects = func(query api.FindTransactionsQuery) (transaction.Transactions, error) { + Expect(query).To(Equal(api.FindTransactionsQuery{ + Addresses: trinary.Hashes{"GQKALAAKWLAUKANTFPOZIWOGDNODDYZGGVKWISNYWFD9BAZGBMEWZRSIG9QAMRPBASMCLUACFICJSWRBWBPRBFMPZC"}, + })) + return transaction.Transactions{ + transaction.Transaction{ + Hash: "YJLLUHLGJNZNMT9JGIHNOTPUNHHZWSOC9NCUFYYUNX9VNVMSWIDYAFBYTBBCVMJUOLDZESRWTVZX99999", + SignatureMessageFragment: "HH9ZURAASMZCC9EESAHNIFQJRUEPETKZCWWYDHXHUYZOUXCUI99TUATEHWVPIZQYHWYOBNSQWZKKUVRZQHTPJEPAJFCBWOXRZMWUACSUUIADMCMV9ZYYNAYLDZMFSHSAVVXNQOCZDOGMQVCJLEBGAYNGYJKOPDHBVDKBGVVCSQIBAHVFJOHIKHQKSDMTCVHIAQUB9ULLSSUUUVKI9X9AYY9FBRWZDONSKVFQRIEIYMFFWVHUT9EZMDVSDHNTWAIKEWZFUARBPFJZWEBEYRDRNSSHOQWJAWTSYTFMYLDGTTXMWCMALHJFBAYIESEBNBEZCZQJGZPAHDADUULD9UDRXCDPOFIPKBLL9RUPPIMH9MEJSUCWGQMKBVFTAZSOFPUQHXUQBJCACNHCHGJYVGHJPUKSMZQWVSPBTQOGTALQUQXUCVAXRBKRSJZPXQIWBRIIEOC9JXVTBNEBAOZBOOOPPTNKMYJCDEFJJQXAIOIBLCSPFFSDGVXYKNUPWMVOEMCJXMVLYSFFRBUAAYXROCEYIER9SBBGJRJRNBOBKAZWGSEY9FK9GJBFZY9LBBT9GXIAKKCQEBZASGUSDFDUC9QYSTPAXPLLEUSKIMJDOVCAFYKSP9WGB9A9YDAQCDNTHJJQWCZHIQTQYKPBXMTWNSBKZJESTVURV9PKJFAEEUCVFXXMNBMOE9GTYNJV9TUJKPQEXOIEN9NEZKWXXDZBTIGXPNBHMNQGEJ9HOFGHUAAVHXO9UBNZOQFKFRDTIOKBCSPYPZ9QEATBLCHXXPNPEXTYGZHDRSYVCMBGK9XQBSECRXJSKD9QFFMVAFDAIYWICUSDKKTKMPWHCHMMNHDTGXYBLTOQTUMQLPFLFGLWEALWFCFEYXQKOBTVH9GIAFHLNQDPIZOAPMVRFWDQDQKMNDJVPCNL9IVRDMEZSVUSGDCH9IFUKQZCJUEMQMQFGTLWTUBGHBYLKKWJFWUVTVLMQALCIPTJUEMRXAGGXSHJQCYZXDNTFL9FHYN9JTUTWJBJWMKVPDXFIMOOBBRUYP9SZZHNKOHCWY9DNQWZKDYTMPCD9OKRCQXTZKIIMGXNBKAAQVBHXULWRGCEVOSPFJVKGIQBGUUVEVHTCZBQOFOIERIYCDKZ9JCNPUUQPAQEM9UXOMBICNYBNXIKTIWVNNUNJCBOVOXV9YCYQUPJJGRCOUVKSELOQECMOACRCHHTSFWWQGBZT9QYTQPDTXLVVPQXADFDR9DMEPIVIUTBJYBDVROEIEVPTBLIZWI9DEVCNLTWYILRQTRKYNAHSEJJMZLIAOF9JSWPHYSWSRYDDUMMTKIACLFJGQUXKFFFXHGHXNTLFHUKHMW9REHQSQGZHMMXMIVAPOPM9IULJPMBFTHOJIZDECOFFALTSJXEVBHNPCMWWJWOVOOPQHHBGMMXGKAMMOLHHJVYCCSQMBWMCXPRSIKLEFZPLXTEUPITXCQ9AUMTAXORLWUJZBZFCYHAHBDKMMCUVQZ9VUJQCZSBRPEJLWNTGLKYRGTOEMVENSRJWPBVYNNWBZBMXTXSYXKIOCPYOKVNCVIIHFIWHJPYVAOYBW9SCFLLUMQACVYYTWZK9SBPCPAJTRRJSBDCXSBSHTHDEKKXWKYBH9TCQGJOQRKXMFVDFOFNXPMVB9XVYSJURFGPMOVADOCQXXRKOOZYLKQEDKCKAKGLICDPGCVZV9TQBIHJFPKFVO9VQDAWTKSPFSHJW9D9VRGIQPGJNFNMGVXBOOUKYCCJNW9VSFLYHLJIDDXEBKXYLQNROYQSPIZBXETKOPJXVEWCVWZTVEZMQJYTYPYEAQTQYOXVNKRFGOZXZQIKOBQSUHVMXRKXSEHPIAZPYGNZXXWB9COQV9MVLSXZJICFLEOVLZFWIA9JUIPFIOHXFXVBYXOHKY9RWMEVWCYRDHBEOZ9MAPOFT9WNEFBBURIQUN9RUSSM9FQFUSNOPTZLJIPYV9SDCO9EJRMQIM9JPRYCEUTM9FOXNRIZTGUP9XKXLGA9CNYQEGKOQOAZQEPIMDWL9XAPCIBAVMMZBLIQEWFWWULEPXIMPBJORAHGSMCHDNSQMMWVKGCGRLFLA9ZHHMJVCNNSNETRIBQTPDB9MX9VSNSEOQIK9SZDITUMMESYKJYD9YKZKCOYIUHLYOZFWJEJUACE9STXQB9RHLL9YVNITZIULZYUJNH", + Address: "PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTX", + CurrentIndex: 0x1, + LastIndex: 0x2, + Bundle: "FGEYEVYLIYESSZTIBXAIDQSK9QSJSX9JFLYKHETYIQRCWVRBVWIDQJBEUSHOIKRLJXLPSGWALTKFXSVCW", + TrunkTransaction: "VSAASONOCUUTNIVXUJ99ES9ZPPWVZOZBAIHL9ALHYDLYNFXSDYIOVUFHBZKVR9ETZDZHI9YHZZYO99999", + BranchTransaction: "CTHMUYWJYAICTJDPVKVIHDBLYDUKVVQPWKXAUW9FATKLSRUVSRTYAHYMTXYNJSZZSAOEXSXSYOLTZ9999", + Nonce: "POWSRVIO9LVB999LNVMMGPTNNNV", + }, + transaction.Transaction{ + Hash: "VSAASONOCUUTNIVXUJ99ES9ZPPWVZOZBAIHL9ALHYDLYNFXSDYIOVUFHBZKVR9ETZDZHI9YHZZYO99999", + SignatureMessageFragment: "RDYUTFNZSKEHVWXFYVXUWYPBEUFULLNQRYYRHJRYNRMPGSIVRDPIAYIVKZNLECLRPVCOLPCVLTSGHCDSYUEYAV9QPLHO9VJ9AZQRNZQSJBNZCIMMBAAGB9YXKINSN9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", + Address: "PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTX", + CurrentIndex: 0x2, + LastIndex: 0x2, + Bundle: "FGEYEVYLIYESSZTIBXAIDQSK9QSJSX9JFLYKHETYIQRCWVRBVWIDQJBEUSHOIKRLJXLPSGWALTKFXSVCW", + TrunkTransaction: "CTHMUYWJYAICTJDPVKVIHDBLYDUKVVQPWKXAUW9FATKLSRUVSRTYAHYMTXYNJSZZSAOEXSXSYOLTZ9999", + BranchTransaction: "BZWSJSIGOCGELCBNELWXHQKNXZVHWSPFMIMI9CMBEZITWJNVNNCOWNSVQBFLIUILBQSUUCUIFGPW99999", + Nonce: "POWSRVIO9LVB999UNTTPPPVNNNV", + }, + transaction.Transaction{ + Hash: "FPZSZEPOSNZVCMDNIJVBNRJHMUXBKRIFEHBERVQCRJXJCIAXGEUHFJKKAZTCDBNWCFPZYUADQDPA99999", + SignatureMessageFragment: "AZBCBCDTA9YWALKBOBQCBHBEOGVFTJGQGFVH9DDBETQCYZLHSBQQOEXKHVCRLBOTABTUYEJDNHXOBLWNITRDDKYCJOJVSLUDPBOYJ9WMLNDFWXKDAAJZDAIUUMLPVQMWIYJYE9G9BEIHBOQBKZZSLXBKGPDLPYRFLJPUTJOAJZELNGZFQBEPP9KNFNBQCQIBANQFLOQLQMFNMKDYJITRVLPDCWEBENYVT9GVPELRCMOHXPTCJMEEQLQGM9QG9WKHWJTBGXBVALITU9WIECZMQJLADX9WPIFB9QWLKBTEL9FTKLAQENDRESAZNSYGWCDMKO9GKVED9SXZKOJONWWIPKCTCCFBWYSCFAJPIQOCCCPLXZSNQJ9APQPYQMHQZBICSTJQIIHOSJKCZBFTMHEKPHOVYUXTBGOV99BRHKVFXOJO9QUCFKAUCZCSIFHNBRUNWSXOQAHOYHKHXLBRDSWHMPFEXLVEZWGLNAJOQSANSEJM9UOORSAWTNWIUPM9OJYMYHZDCF9LQYYYYCPQ9MAU9ABZPLLDTPISQBKIZMSOURGLKSQV9AXPFOBIPUNBQGZOMUGLJYBDXXWRJYOIXQTRVQYYOZAJ9YOFIVFBMRABSJFVNNMTMPYIEGNNPQEAPDXYYIIRNYVYUN9KAICLDLEDMGM9MKMAOMKNJTFJFXW9KRDELIPXMTSQFKHVMBP9VOS9SIFTCTHFQHNLAABDYOQDRUSI9MFUVTCWZB9CWRVZZQASQROBORXB9K9RXYOQNTPC9CNMHRISBQFSGNBGAOCYEHSVGV9PG99CUGFPTINEBWFUXUFICLWSVFGQVEJIUAWXGPXXXSTGQI9XHETTXFGYETDNUSVSAPNDOBVNWOG9AANDIIOKCWGF9NQYPMEWQPZGPHFIZQPV9BJGHSZGJFMXGJBMSVWFZYCSEYOPGCNJOZPSNHHYUVUMCHWYDJJDGNRJZXDTLWSHZVBVN9BLQKRAKKGQAYVPODVJDSUJRZHKKKPXVURBAACAUFUANUSCVFIYAUFDRHGHVNNBZVIVLJWNJTILTHGJM9UJSYBBZKOIBQXFRGEELNYLKGXWCUGXWL9XANUK9ARKCDRXXGTPN9OIUUSLJQJMYHMVVXMRMBXIAAZMRHZOPYOLLUAZCFRJSLRMXHPLGJCWOCPXRUIRXXHMKWQIQNZMANMAAJKDOLOLNKGESNAGKR9DCSJVWJWXKD9C9GHCKYRZWMPZUOBUCOAHAFINTREHCTZJCOVRDIBOFXMHHODGWSYBOQHQNG9PEOHUZXJRAKAOOSBNOJJAFFVNHLDKBVZIXNNINO9UDVMSEIROQBHYLLRKRMGAC9ORPOHABXJNXBHTIRMOPDLGSEBCNVEGCJIYZ9TFPHYAKICGBOMIHHWUAPFYMNBBRJNLSDWOVUBOIDJXXULJ99EHPYMLCDJI9NCORXTV9APH9YJSTOHUE9ENKFRKTSXT9ALCZNRYVHFYGSAHIKGURPCYPIUNGVRMDCUXXQXQOBRJRKQRQYMCDBCD9RXBFNBUARVRFTZLXZZXWWAFJKVBRGQDUUBDYUDSUXYOBPJMYTWYWPENKZOYMW9PALW9PCJZIXXUJ9RRVKDSPU9AGVPIZNWVYOECDOGZSTYCNRIGBKYVKRYFQJISNBNJOSEYED9HFPYJY9LTSIDYEUAVTUWFYGUHUNDPPVSPVZXPWUPHWRPULJYLODC9HXTZVJESQ9GQG9B9KTUYHIAGXVXOCZLUJBSKWTNYHGRAPTINBUINDHAVOINYETKGQ9OPMHZUQJFGWMPT9TWKHBMDJAWBWFIVSRBOLEZOYMLEDEDGWSKKIKNCYWFUVTFLUHEDHIOQVJVXXLQXETUSZJLUZA9BVTJTTMKSSXBPZLEAZ99XMRJBOHTMGUQSUXEFNKOKXBBNDMEUIBXG9ERVNSYFYFBGLJGIPPLQTVMVHBEMFNXLREVZEPHKKCAJNQ9AYPIXYUUUFACDEAJPHJYEQOBHUKCYTNLVVYQAKJBGH9UCSNALCEOGNBURFBTVFCNQHJ9GVBWQWMGPCQPEIKPSLLD9XXDC9XOZLPYINCYDJ9WPLBQADOI9GLAVGORRCPZDLTFKQLUDSRMFUHAXEVCVPBXQNWANKXDFKFRJ9GNIGATVAAMOFODQDBYMAKUXFOPZFAMXVAQXULGYLMTGHMONDZAXGAGYLSG", + Address: "PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTX", + CurrentIndex: 0x0, + LastIndex: 0x2, + Bundle: "FGEYEVYLIYESSZTIBXAIDQSK9QSJSX9JFLYKHETYIQRCWVRBVWIDQJBEUSHOIKRLJXLPSGWALTKFXSVCW", + TrunkTransaction: "YJLLUHLGJNZNMT9JGIHNOTPUNHHZWSOC9NCUFYYUNX9VNVMSWIDYAFBYTBBCVMJUOLDZESRWTVZX99999", + BranchTransaction: "CTHMUYWJYAICTJDPVKVIHDBLYDUKVVQPWKXAUW9FATKLSRUVSRTYAHYMTXYNJSZZSAOEXSXSYOLTZ9999", + Nonce: "POWSRVIO9LVB999OEKKKKVTNNNT", + }, + }, nil + } + receiver := mam.NewReceiver(fakeAPI) + + err := receiver.SetMode(mam.ChannelModePrivate, "") + Expect(err).NotTo(HaveOccurred()) + + nextRoot, messages, err := receiver.Receive("PN9CYZTVHPKVRVBOOIRZDZSHQLURKQYSQTTDOGAKZZ9SCGIWTTOBPRWVPZRHZJHIWOKLZE9SQJWGPGFTXUMUPGBBN9") + Expect(err).NotTo(HaveOccurred()) + Expect(messages).To(Equal([]string{"Hello!"})) + Expect(nextRoot).To(Equal("HTGAUZTBH9SIVJVREIXLGAPPVQOFZZCRWDYSDG9JLXLFREYRWBEOBPSCSFTV9XZNIVZNAZINBAFDEWUZ9")) + }) + + It("Should receive a message from a restricted channel", func() { + fakeAPI := newFakeAPI() + fakeAPI.findTransactionObjects = func(query api.FindTransactionsQuery) (transaction.Transactions, error) { + Expect(query).To(Equal(api.FindTransactionsQuery{ + Addresses: trinary.Hashes{"UJSUOWHILOEKGAXCPUVTMNNVOLXSGIBFIVWVXTNRZF9AEIBOGTCGBXWGTNZJIXPALAQEFPAVNRYRLMOASZBZBPPVBW"}, + })) + return transaction.Transactions{ + transaction.Transaction{ + Hash: "GJTUXOBGJBWYZVWMQSRHSFFRKRWAVHOFWJKTWDBWR9DXLTUUXXZWAJEOMNFBSERVVVQUZWHOCYNWZ9999", + SignatureMessageFragment: "AZBCYUYKDKQHTDPALRTJWQQKAIPNKSDLWZTLOJPTHCHYXJAYPUMPPDUAKDDLWPXCLNQVGSCLSKZHHOCOWPLGXTNHUEABYXFPYLBSMYOLQHIUZCJRBMMFOGKBPOFNPDK9JUEDVJNMU9ZAFXFYBYLAULNEBHLZMCNKADKAVXJVTNALSWEQ9YSRIKBNFTWUIGWMEWZTDSIJA9MRJXZQOATEENEBNJDQFUBTWAZSVCZKJCANYOFG9HAMADCXQMNXIHVHVPOGXQEYIFDOODAQRKRCCGUOKEFIDBWCRVDBGUIEVBXRC9JRTSXUQUFNQOHLXETIZDNIOIHPGRKUEEGEMVTQZUDEMHVRTFVKIGCBNGSHJYPWVECQDKUWC9VYGGXYHHZGWVIC9CBDCNOY9XQQIEBJFYU9EGV9WETXTSPCSESDYLMPLLBBUN9IIXKSIUQIKJUAXQPDLELNNXOKKUVABIFJPVQKWFXT9HZBTBFL9CTWLKXCOVGEYFHMFPL9GRMFOEFOOWZCTRFZZDNDSSZDVNQBBDVFGHLBPGH9MRJCFPRQSMGDTHZTLLWWJVZINVLXBXUEEMVHNGQFTXFYYI9AWHUOFRKTGZOKNRRIGLKNLTDQRUCEFEVEEUPTYWXFFWDCBRCPPMPKMXYUXYPWOJGADUBAKHPCBYWVPHY9LOVQJZMVSFNIU9VSUUNTGBHHTLNRAQFDXITLKO9GLKXMMWQIJKBTFVNNMDSUSTUDXEONEXRFMQHMLDOFYEUYRXIVUU9NBXSGTCVCKSNXUJVUO9NOBSIHMNRGMDPQPIWXFJUAQS9KMOZGCGNCREBZWCQTPYHPJYWODKJJGYEIMXPDCQMPCLADQ9YLS9NGMZALPUXITZDYWSZPAPXCQDUYK9SGFORQ9VFSE9JJLKJPRGUVOIJUJLZIPO9ROSQKZIO9INF9VPSEFHFAPJBZSEVXLLAGGOYDR9BYWDM9GM9TJTMZYSRVWDVQFHEZH9RPTTZWZQBMMVSFORPRFCWHTWAVRTJESVJUOUETKUAX9VSOAMOK9AMYFQSAQLVIEIJFYOFPCVRCCJDJTKCJDJLNLMWLJVKDWQUJBFSQGFDLRRDJRMRGVUDPDUJM9WFAIBO9ZIGHYBBHLSQCPRGCYUHPALZBYKRHEHFMORGI99YKAMQINYSWWNXSTIWOBNYZVK9WHZKTXDIYB9BLJUHIIYTNQFIVEVUH9ZZFDWSHBBZV9TTIMDO9XPV9ORPORLXXOHSSHNVBWG9WJXMBSUWOEVDMVZLSJEDN9DENTHHMJACEURJCFOQNXKNXRJWQKOVNNGB9NDJUWUGQUQRLAWVYRIBMRUGQRGJLEADTJ9TVQOKWSTWSSRVPWTSAXLTDBECR9HLBEWJWYAKWSIWHLB9VYENQELP9UEFXZCLSLUSXSVYNJYOQRUNUAOHEKTFOYMELZRGOECGTIUXYNFBEIYKZSVBCFIOIJUABAOMSFJPASMYYGBVZOBZSRMWQJRQKFJPZZ9W9HY9PFEDHPLCXHLO9HXRLAAJXXZ9NHYZNVLPXTADCHRUHYNVHMTJBMNQMYYFYENYSMIOQZKPLSDNSOBXHLBAUXEUVENHZOTHTHWKWEHRDNKFWAXFVCVQKERQXLTSJTDHLMAXVYXCZTOWCKKSYESQYQZICDIQRACDBJEEKWEUQWSSHFHVGMNPUDLAAKHTZPLDMQR9KGOTGZUNOUPQICTYUNHORGAIWVLEXQ9XMMTDVGXUX9GTPAFOLOYIDCATZOPVXXXMXZQZUXUWEFMLJPHJKWENXTBFJXKDDWFMASWRSOXJBLDMVBWYX9MEDGMQVKQITFZOV9QKLMDVIKBYVLLVNDPOOWVOI9OEGOMLEIKRSSOIDLBLGLSFUIPTPFHVOCOXQKYNQJNQTDCQUBHIEBIPVDBZEXWBWYPOJWAVPUQY9GDBJ9LQCRWFWNQTQIYWHR9NBCXRZTPSC9XPGSIBNREANGLMUIYISAEFWU9WCIKKAEAYEBXIFZXYIAYZB9KZJEELWXNIYOKMHVWTMJGXJEATTZUXQXMAWGKJYOFSMHUFOEZNSKGKMIKYWXJTZBWHFBXLQBLJKBONLCFXEUWUWPWCHIY9MNBVNDGAUSHUXQKOT9POWOMLXUSMKPQYUALJMJBYHOXOFKPILWAREBIXURDPIKAQ9EHLZ9AYBGPWMXKZGXLBMCYZ", + Address: "UJSUOWHILOEKGAXCPUVTMNNVOLXSGIBFIVWVXTNRZF9AEIBOGTCGBXWGTNZJIXPALAQEFPAVNRYRLMOAS", + CurrentIndex: 0x0, + LastIndex: 0x2, + Bundle: "LSDLDKZKPXGGOJW9KFXF9OKFI9QWPBJWVJNKYDVBZFFJJFUWGBBYSEQ9JNJXFJYDHQLIOYQLJFPAJTTXC", + TrunkTransaction: "XMXNDJXZW9IPFRQTOTNJUMFXUD9GXFCNYPPNMNXAZSZWQNYIXBJOAXRGXBJIZVEXMKT9MGRS9JUXZ9999", + BranchTransaction: "ZWC9GOJWUGGEAWLHZWKOPAHVMIPI9BQTOAZJOJASBOAQE9EUDZZVOOKUBLNMJNGJAZUMASKGBJJP99999", + Nonce: "POWSRVIO9LVB999ONGPPPMNNNNP", + }, + transaction.Transaction{ + Hash: "XMXNDJXZW9IPFRQTOTNJUMFXUD9GXFCNYPPNMNXAZSZWQNYIXBJOAXRGXBJIZVEXMKT9MGRS9JUXZ9999", + SignatureMessageFragment: "IXKPL9MYDOKZHSZLP9MLNXVT9INMCBLPJZAHJTUGOYYDCHHEQDZKBQTBMTDLJGAVREPUKFM9ENCTLCNX9ZCISAKGX99QUMF9NIDUFFQGXSBBQWXPFXORMNAERSTBAIBKDXLTFSDQCOAAJMTQGIMKABIULYMRYYDXMADCFYSZUAAPMEBEZMYIRSFXUNGTEPXTVABSBZFYY9XZCJTDQ9EUTPMCIKETIEWAJWSOQGRKHSBOGNDKXDLKECVIGUGXRTETHYVVTBJRQWTTFFCHLAXNIZHIGBTVHVUZAWKABVJZNGBNTWPZMKALNJKGRFUFSYANYUGMJXL9SJPRCXAKXGXEMFSPBLWXZXVCOPJPEUJMIEZD9YZVFQKKFZLFZUKEBXPJZABSUYBGWNLFEBVFNFLJ9YUASFWXMADKXYJSENKOKNXQEWLCHVWVJXZIGXKO9LJYZYJACQWADZNAZUQZRDNUZXSZNLPQIQRZFZFY9JVLEVCVDQWQOFYLSG9HZMGCQPVR9VI9QHNSOTMGKAV9QYOGGDWCMINCKYGG9JVNLKXPXPJIEYBHTTAJCRYWSPAFSLPSKGV9IOEXQRADGZWJVIMWPJQKFQPSDECNLGYJJFFWFZFJWAUWWZGIUYHSJCKPGRBOQLKAUNASYZIF9QEQZPNWMDBZEZLZTS9LNYXVGIQKKQUXYETLOPTZWVKQXBYBTWVXFRGDLSINOOSPOMPLNMAXQLYHMZBLXLBSQRNUUSVWPJTQ9JUXHOFSPTEX9XQZMQTTKYKFMNVZYKPZ9KIVEAOVGDVVMKNPSNKYJCXSJN9JMKTGTTNNUICRUEIHQBXKSACNRFORSGPCJQKPXOXDHDWMINLLRDEWWPWIVZEN9ZVMLSCFXSMIBGMYSLHC9KZJQOYJDLDYDTEXQWJRPR9IFI9G9ANSGLZY9YSDLZJTTNIWMERV9VAW9LJZCJTJRBSQPHBULXMNOPMPVFCNCLPQGJLBBMDVECGGMJCSUAHBMJACVYBVISQEMLC9QFVZIQYU9LRQQSWVJCTXLNICAWEZQGLPBKBDWUZVZBBTJUPIZDIOGYBROTOJWNJLUAXAYADVTCLXGIKGCLOSSK9OUSEWYXGSXGORYOTPZGCNXHY9KHMFMKNXMVVUIPNRFKKKBYUHYYIPNBNLI9IOZKXUCHWSNTAMZLXZZWFNPMBZMKBVJZHVXGR9RRAUNNIHEWUJLZOWKMAOLI9SZSQLVERVADQZ9RMXI9AZKDDCDHO9ZTNAPQ9ODNAOODCFZKVZNIPDR9NECA9WBAWPEVDCNRTHUSWAZMUZXZDXCDDPUXEEEJCPUZEJUGRTPCI9IRUETPANTFEPRQNGSSOGWEALPLKDUJN9MANIEMGYCYXEZTDPNLBH9UFQTETUHQYBDKDNWLEVHORRGBPPSUYKNJJP9FTFWUFOWCSV9JTFBVRQ9VVBRNVQRFAYPSKXMPCDSXBCHZC9PAUEKVYDWGMYYYUIANUTDUKCBQZFM9CSGTBZVKX9INKRQKUELMIXPLTIXZKCGVZLPQ9CHWVYWA9NUEFBRDQJGBPV9AQ9DBWWHWFGOZEIZDNHRBJORFFMDZIBSXDUYIRLMLIHAFBMEWXSKVQRYENNWCBFKGETVUFHTKSKOFVYVFUTZBKAWKJAOUMPNRZPLRUGADPRWNJVHOEEPQ9BYBGEC9OVNHQPLFEWNRLXTFKHIGUXVIGJ9RKJBK9ETLWFN9LGLWBYQVGI9WANBUVIUSNYTWTDCUFZEGPSDALQWOTXSDYRMCYWXWDHXIZOWVBIJNXZWAFBIKHPZITSMVOBCLTBXBYESUKPEYKVWKIZPDSJAQMBZAYLNZQWYVC99ADTTCXYRLXCQRQDONYGRZUOJIDDWLNJOTCNHULNCGUNJJLZOFZPMAYXPONVCFIUPJETYWQPCUNGXHATMLYQMEIWVZLCWWFTTIHPBGBVUJWJAALZOMSANDUUXQDESR9DDVLGVDAEMPBCGLQXMXDMDHYOERBPACDMMQVPENMDMWYJJHWZRNGGISFVLNFDURHTZZTEQEIBOLIRFCYGSYMZJNOARGWCTRJUUIJESKA9GXUXNCWVMFWVYABIKPFUNOWIJZGOKVWRMPABGQGMMGJRMKMCDPJJFNYKREUDPQHPRVLQEVBGHMBR9RPDMFINHNFBNWNPKUJRLGEHALIFAFANJORUDGZ", + Address: "UJSUOWHILOEKGAXCPUVTMNNVOLXSGIBFIVWVXTNRZF9AEIBOGTCGBXWGTNZJIXPALAQEFPAVNRYRLMOAS", + CurrentIndex: 0x1, + LastIndex: 0x2, + Bundle: "LSDLDKZKPXGGOJW9KFXF9OKFI9QWPBJWVJNKYDVBZFFJJFUWGBBYSEQ9JNJXFJYDHQLIOYQLJFPAJTTXC", + TrunkTransaction: "NEBGYCXYRRENUW9KHA9NVBFISFBXGNACSINPPRBODKHQLHA9EKTDGHUIASJBFFBYLDBSSREOAYWUZ9999", + BranchTransaction: "ZWC9GOJWUGGEAWLHZWKOPAHVMIPI9BQTOAZJOJASBOAQE9EUDZZVOOKUBLNMJNGJAZUMASKGBJJP99999", + Nonce: "POWSRVIO9LVB999FMVPPMVNNNNP", + }, + transaction.Transaction{ + Hash: "NEBGYCXYRRENUW9KHA9NVBFISFBXGNACSINPPRBODKHQLHA9EKTDGHUIASJBFFBYLDBSSREOAYWUZ9999", + SignatureMessageFragment: "9QOJCZIKQ9IMMKBSWTJ9AVBABAPQHQZIVXPTDKGODOQXXQEHMBOMRCVUZGUTOPXKRKGDLERPPQHBSYCI9XWM9ESGBQYZUZJK9KIZDCOELQHVHRHLHOB9PVSRWCGPUZ999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", + Address: "UJSUOWHILOEKGAXCPUVTMNNVOLXSGIBFIVWVXTNRZF9AEIBOGTCGBXWGTNZJIXPALAQEFPAVNRYRLMOAS", + CurrentIndex: 0x2, + LastIndex: 0x2, + Bundle: "LSDLDKZKPXGGOJW9KFXF9OKFI9QWPBJWVJNKYDVBZFFJJFUWGBBYSEQ9JNJXFJYDHQLIOYQLJFPAJTTXC", + TrunkTransaction: "ZWC9GOJWUGGEAWLHZWKOPAHVMIPI9BQTOAZJOJASBOAQE9EUDZZVOOKUBLNMJNGJAZUMASKGBJJP99999", + BranchTransaction: "OFVXJEDJFKSUVKEHTIHBLSCCLRHTMJGTAQTUMHPFSRLHNOXWEEFFHRLCIRJGSBMMA9XAWKIAERFQ99999", + Nonce: "POWSRVIO9LVB999UNMMMEPNNNNP", + }, + }, nil + } + receiver := mam.NewReceiver(fakeAPI) + + err := receiver.SetMode(mam.ChannelModeRestricted, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") + Expect(err).NotTo(HaveOccurred()) + + nextRoot, messages, err := receiver.Receive("FBVSGGJWUJFVTQCASKBVO9PJAHYZMHU9SVQUGJJLET9UQE9YT9PLOJWENULIVYMOLYSNEM9UUXFCAJKVJ") + Expect(err).NotTo(HaveOccurred()) + Expect(messages).To(Equal([]string{"Hello!"})) + Expect(nextRoot).To(Equal("RKIBPEBXNLLXSXCWBDXSKYZEIJZPQUSXSQSDAUPDDSFS9W9BOPGSMKRJINQ9CGFKSUBMTKXDY9RMGJQE9")) + }) + + }) + +}) diff --git a/mam/v1/transmitter.go b/mam/v1/transmitter.go new file mode 100644 index 000000000..1736711a9 --- /dev/null +++ b/mam/v1/transmitter.go @@ -0,0 +1,148 @@ +package mam + +import ( + "github.com/pkg/errors" + + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/bundle" + "github.com/iotaledger/iota.go/consts" + "github.com/iotaledger/iota.go/converter" + "github.com/iotaledger/iota.go/curl" + "github.com/iotaledger/iota.go/merkle" + "github.com/iotaledger/iota.go/trinary" +) + +// Some error definitions. +var ( + ErrUnknownChannelMode = errors.New("channel mode must be ChannelModePublic, ChannelModePrivate or ChannelModeRestricted") + ErrNoSideKey = errors.New("A 81-trytes sideKey must be provided for the restricted mode") +) + +// Transmitter defines the MAM facade transmitter. +type Transmitter struct { + api API + channel *channel + seed trinary.Trytes + mwm uint64 +} + +// NewTransmitter returns a new transmitter. +func NewTransmitter(api API, seed trinary.Trytes, mwm uint64, securityLevel consts.SecurityLevel) *Transmitter { + return &Transmitter{ + api: api, + channel: newChannel(securityLevel), + seed: seed, + mwm: mwm, + } +} + +// SetMode sets the channel mode. +func (t *Transmitter) SetMode(m ChannelMode, sideKey trinary.Trytes) error { + switch m { + case ChannelModePublic, ChannelModePrivate: + t.channel.sideKey = consts.NullHashTrytes + case ChannelModeRestricted: + if l := len(sideKey); l != 81 { + return errors.Wrapf(ErrNoSideKey, "sidekey of length %d", l) + } + t.channel.sideKey = sideKey + default: + return errors.Wrapf(ErrUnknownChannelMode, "channel mode [%s]", m) + } + t.channel.mode = m + return nil +} + +// Mode returns the channel mode. +func (t *Transmitter) Mode() ChannelMode { + return t.channel.mode +} + +// SideKey returns the channel's side key. +func (t *Transmitter) SideKey() trinary.Trytes { + return t.channel.sideKey +} + +// Transmit creates a MAM message using the given string and transmits it. On success, it returns +// the addresses root. +func (t *Transmitter) Transmit(message string) (trinary.Trytes, error) { + root, address, payload, err := t.createMessage(message) + if err != nil { + return "", errors.Wrapf(err, "create message") + } + + if err := t.attachMessage(address, payload); err != nil { + return "", errors.Wrapf(err, "attach message") + } + + return root, nil +} + +func (t *Transmitter) createMessage(message string) (trinary.Trytes, trinary.Trytes, trinary.Trytes, error) { + treeSize := merkle.MerkleSize(t.channel.count) + messageTrytes, err := converter.ASCIIToTrytes(message) + if err != nil { + return "", "", "", err + } + + payloadLength := PayloadMinLength(uint64(len(messageTrytes)*3), treeSize*uint64(consts.HashTrinarySize), t.channel.index, t.channel.securityLevel) + + root, err := merkle.MerkleCreate(t.channel.count, t.seed, t.channel.start, t.channel.securityLevel, curl.NewCurlP27()) + if err != nil { + return "", "", "", err + } + rootTrytes, err := trinary.TritsToTrytes(root) + if err != nil { + return "", "", "", err + } + + nextRoot, err := merkle.MerkleCreate(t.channel.nextCount, t.seed, t.channel.nextStart(), t.channel.securityLevel, curl.NewCurlP27()) + if err != nil { + return "", "", "", err + } + + payload, payloadLength, err := MAMCreate(payloadLength, messageTrytes, t.channel.sideKey, root, treeSize*consts.HashTrinarySize, + t.channel.count, t.channel.index, nextRoot, t.channel.start, t.seed, t.channel.securityLevel) + if err != nil { + return "", "", "", err + } + payload = trinary.PadTrits(payload, len(payload)+(3-len(payload)%3)) + payloadTrytes, err := trinary.TritsToTrytes(payload) + if err != nil { + return "", "", "", err + } + + t.channel.incIndex() + t.channel.nextRoot = nextRoot + + address, err := makeAddress(t.channel.mode, root, t.channel.sideKey) + if err != nil { + return "", "", "", err + } + + return rootTrytes, address, payloadTrytes, nil +} + +func (t *Transmitter) attachMessage(address, payload trinary.Trytes) error { + if err := trinary.ValidTrytes(address); err != nil { + return errors.Wrapf(err, "invalid address") + } + + transfers := bundle.Transfers{bundle.Transfer{ + Address: address, + Value: 0, + Message: payload, + Tag: "", + }} + + trytes, err := t.api.PrepareTransfers(consts.NullHashTrytes, transfers, api.PrepareTransfersOptions{}) + if err != nil { + return errors.Wrapf(err, "prepare transfers") + } + + if _, err = t.api.SendTrytes(trytes, 3, t.mwm); err != nil { + return errors.Wrapf(err, "send trytes") + } + + return nil +} diff --git a/mam/v1/transmitter_test.go b/mam/v1/transmitter_test.go new file mode 100644 index 000000000..46958a7db --- /dev/null +++ b/mam/v1/transmitter_test.go @@ -0,0 +1,153 @@ +package mam_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/pkg/errors" + + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/bundle" + "github.com/iotaledger/iota.go/consts" + mam "github.com/iotaledger/iota.go/mam/v1" + "github.com/iotaledger/iota.go/trinary" +) + +var _ = Describe("Transmitter", func() { + + Context("SetMode", func() { + + It("Should set the mode to private", func() { + transmitter := mam.NewTransmitter(newFakeAPI(), "seed", 9, consts.SecurityLevelLow) + + err := transmitter.SetMode(mam.ChannelModePrivate, "") + + Expect(err).NotTo(HaveOccurred()) + Expect(transmitter.Mode()).To(Equal(mam.ChannelModePrivate)) + }) + + It("Should set the mode to restricted", func() { + transmitter := mam.NewTransmitter(newFakeAPI(), "seed", 9, consts.SecurityLevelLow) + + err := transmitter.SetMode(mam.ChannelModeRestricted, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") + + Expect(err).NotTo(HaveOccurred()) + Expect(transmitter.Mode()).To(Equal(mam.ChannelModeRestricted)) + Expect(transmitter.SideKey()).To(Equal(trinary.Trytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))) + }) + + It("Should error on undefined mode", func() { + transmitter := mam.NewTransmitter(newFakeAPI(), "seed", 9, consts.SecurityLevelLow) + + err := transmitter.SetMode("555", "") + + Expect(errors.Cause(err)).To(Equal(mam.ErrUnknownChannelMode)) + Expect(transmitter.Mode()).To(Equal(mam.ChannelModePublic)) + Expect(transmitter.SideKey()).To(Equal("999999999999999999999999999999999999999999999999999999999999999999999999999999999")) + }) + + It("Should error on missing side key in restricted mode", func() { + transmitter := mam.NewTransmitter(newFakeAPI(), "seed", 9, consts.SecurityLevelLow) + + err := transmitter.SetMode(mam.ChannelModeRestricted, "") + + Expect(errors.Cause(err)).To(Equal(mam.ErrNoSideKey)) + Expect(transmitter.Mode()).To(Equal(mam.ChannelModePublic)) + Expect(transmitter.SideKey()).To(Equal("999999999999999999999999999999999999999999999999999999999999999999999999999999999")) + }) + + }) + + Context("Transmit", func() { + + const seed = "TX9XRR9SRCOBMTYDTMKNEIJCSZIMEUPWCNLC9DPDZKKAEMEFVSTEVUFTRUZXEHLULEIYJIEOWIC9STAHW" + + It("Should transmit the given message to a public channel", func() { + fakeAPI := newFakeAPI() + fakeAPI.prepareTransfers = func(address trinary.Trytes, transfers bundle.Transfers, opts api.PrepareTransfersOptions) ([]trinary.Trytes, error) { + Expect(address).To(Equal("999999999999999999999999999999999999999999999999999999999999999999999999999999999")) + Expect(transfers).To(Equal(bundle.Transfers{bundle.Transfer{ + Address: "YKOLXUAMTJGGIVPEWHUCSKKJWIY9PWEFABXYHAWDBTMOPKWNXOOQCKNHADSZP9SOSDFEOXPVTWUWFDQNHJXGHIWKOA", + Value: 0, + Message: "AZBCERWNZGTGNPUJYARSZHHYKJYXVRWNZODXYRP9TQVT9XX9MMQIDEJUFWNQGTTIMAMENDXVQYHSUVSBXGRAGYSYMFQQTKBKBBNTPABQVVUIYFAQYZGYQLGALVCJCSFNRRXUEODCO9DXHHNMITGJPLISKXXMTRLGRWDTQMHBXZPRWUCPIJEBPWVERLRNGCZPGERXWVKFLCDSZDQ9WQZIYY9MZHRRUJ9EACMREZSZYPMGQV9DUBFTAJEFPCDKWGQTANVYMIJIJHZGMWFCNCBUIXHXG9CTLDTZBSMBWEHNITUOABCWWCOEPAKRFOSZLGTFRNUFKCYOEPKFUSZQPFYODAEJRSNKAUOOPXPBWNXASWXDLMNCFEQWXDQBPKWIZBXNWBHCKTCUPERYDESMGOEZYUODWMHENRWMVKQEQ9AEQPNAFLNKZUMXMQFLGDZBNVQDZCAGQUFCFFCAFYUVNKQLTAO9BTSOIQ9IQLEAWFYACSNEIYVYWNVKCGCKZNGPLXTDNE9PKJBTXXQBDBKH9ICLBUVDVIGXUVVCSCVSLBKVRXBCJHJCRKEYNHSPXIYMWEIWQAIMTYWLOZQTBQNEXFWUOAOJ9LMBXSQQA9DCUOWOGW9JQYWXIVULMDLFVAFPYE9JGRJQLNQTOYNPK9JSB9BERTEICKVMXMLNNOCHTKVACIKEXUXZTQXGTXQMEB9SLDTOK9IFLRXYSKAFTCZVLPR9ECQVNQN9LWYRD9XLYPCKNBBCIJKCWUVGWMXMFOJLVONBKQGIISFWXAOHGUOWCM9YTCUKXKIEO9PCRBPMIPTQXQCLWMWGJHQJMAZBVHIOS9CZBAIGBX9VEKWGEQSMZVBCZNTKNJG9FEPSNLNYAVXTHLVHDTXGJCBRAXGWKBUPIGMRGFOUYFCLUXNNDDUYTFJPHTZYFUNTZXRJRTNSFYQHBSGRJPAEPFIEQJPYXKUXOUCQOXLPKRDJWAKCGWRWGJGWCOUMDDBCTVVOPQQYQOIIQIBKGMBVBPDFKNJVXVPSMZWVWXPWLUZPSXOEGTBXKJTG9ORUJUSMZXFDMMZWYRNMKGTBLXIPHVW9BSDR9LOWGSBHHBUOSZYLJJDTNJKZQWYMNCCMIVLITMFWXAWWHDCOWBTVCOGLTBILLT9KLHECWIGZVAVR99BJEMTXJRXGLXAZYHIGQOJEPMDVYAVSAPQU9JMSIXKISVTRIEI9KM9UGZKBSRLEGZU9UTJNXQRHTXZNAUDA9DFYPWEFDOOMTZICIXGIJELIUNRHSUVJJQJITOOFBFCPQSLKQKNASCTKIPGJQHVGJWETMQGUVJBISTVXNS9BLGRUWNKZQKUPSFOLO9IJQNDLXVVNZH9NRIYKJGPRJZACHECFQSQXMSGKMSTRRAFJWJJBFZCQAOBITPTMLTVLFAGBBZOYEXDFKWBJNXLUUIOSDWNLWQWUXNXOELSHBVMIMHUOPLJLNFBOQ9VPBXRRNHHEKUHNDLZ9GZZRKEXNGMCXSOPZKEUBHYNTTIHIGHDLBLSCGEQHLWKGHDKJTPXYPYNDWNSJOKKKPAUBDTVLMYBT9ITCIH9TJOPTICXTYQSFTHYX9NCZGM9TOBJLEMIVI9EPJAKKRYSDPSXLHQDWJGHJZFTWXVFVZGJKGGMYJHCW9XLFEN9UUYUUCJGEZIQVT9HKXOSBFINKEV9LWTXDDFDIG9YPWPQODFXHSGNIOBFDROPEJZZECPKOAVXVECWABNPWMQSXYSCJALKMF9HUQXAPWTEXHEJ9EQPZZKCUUBYZSNYGBL9JLIRJNZSVVYUGSVTYGUKQDQNCJFM9L9GW9XKIUGAHDUSAFW9ZURVLYGXWHRLLAKHHDRDF9BVTSNNTRJBPDAUWWFKYAHHUUFCQEEPNVPRFPDAOAZSOXKOBYXQIXER9ZH9JKUTAWAAASZ9NBXNEDSYXGARIMOEOMTENPOVGCZTPQLPAYWHUYOFQKUMMYXKTRYCESVPDUJMVDMVPCXC9GYQ9FSNDQYPDIZJZPMRBXUSKOSDDENXFWMQMELOYSFT9GAPEYRUMHRHNBNJUAIZDUOZPVIFBEMVWXDUPTQQWEQIZTVMOKGXIHACDFFRPUX9CZYLKOAVJFQMJZ9YDLSFFSQAJANI9XSGERSUIFTMQ9AZXHGJGKIGLSXBFZMQVQ9XBOHEQJAYDJBZVIZGSAAUHX9ESEDALVQKHRYCPFEAWBQVTAUBQOANWPFYHKGVQKVOYJQFIYSLRAZPLZJLDZXMYRBN9WTYTOZKHSAAIOFVBITRHRMPXNZXZYEVMKOXDMASIBMQEFBAWISR99IVOUDJFTFENZ", + Tag: "", + }})) + return []trinary.Trytes{"TRYTES"}, nil + } + fakeAPI.sendTrytes = func(trytes []trinary.Trytes, depth uint64, mwm uint64, references ...trinary.Hash) (bundle.Bundle, error) { + Expect(trytes).To(Equal([]trinary.Trytes{"TRYTES"})) + Expect(depth).To(Equal(uint64(3))) + Expect(mwm).To(Equal(uint64(9))) + Expect(references).To(Equal(([]trinary.Hash)(nil))) + return bundle.Bundle{}, nil + } + transmitter := mam.NewTransmitter(fakeAPI, seed, 9, consts.SecurityLevelLow) + + err := transmitter.SetMode(mam.ChannelModePublic, "") + Expect(err).NotTo(HaveOccurred()) + + root, err := transmitter.Transmit("Hello!") + Expect(err).NotTo(HaveOccurred()) + Expect(root).To(Equal("YKOLXUAMTJGGIVPEWHUCSKKJWIY9PWEFABXYHAWDBTMOPKWNXOOQCKNHADSZP9SOSDFEOXPVTWUWFDQNH")) + }) + + It("Should transmit the given message to a private channel", func() { + fakeAPI := newFakeAPI() + fakeAPI.prepareTransfers = func(address trinary.Trytes, transfers bundle.Transfers, opts api.PrepareTransfersOptions) ([]trinary.Trytes, error) { + Expect(address).To(Equal("999999999999999999999999999999999999999999999999999999999999999999999999999999999")) + Expect(transfers).To(Equal(bundle.Transfers{bundle.Transfer{ + Address: "VSGBERKCRZXVDYNHBYCBTEZHQSPJSJEAXSFFBPMNSLENOMGDPUDYXMULBVLUDGROBT9PUSNTMSDQH9WDABDYPTSACC", + Value: 0, + Message: "AZBCERWNZGTGNPUJYARSZHHYKJYXVRWNZODXYRP9TQVT9XX9MMQIDEJUFWNQGTTIMAMENDXVQYHSUVSBXGRAGYSYMFQQTKBKBBNTPABQVVUIYFAQYZGYQLGALVCJCSFNRRXUEODCO9DXHHNMITGJPLISKXXMTRLGRWDTQMHBXZPRWUCPIJEBPWVERLRNGCZPGERXWVKFLCDSZDQ9WQZIYY9MZHRRUJ9EACMREZSZYPMGQV9DUBFTAJEFPCDKWGQTANVYMIJIJHZGMWFCNCBUIXHXG9CTLDTZBSMBWEHNITUOABCWWCOEPAKRFOSZLGTFRNUFKCYOEPKFUSZQPFYODAEJRSNKAUOOPXPBWNXASWXDLMNCFEQWXDQBPKWIZBXNWBHCKTCUPERYDESMGOEZYUODWMHENRWMVKQEQ9AEQPNAFLNKZUMXMQFLGDZBNVQDZCAGQUFCFFCAFYUVNKQLTAO9BTSOIQ9IQLEAWFYACSNEIYVYWNVKCGCKZNGPLXTDNE9PKJBTXXQBDBKH9ICLBUVDVIGXUVVCSCVSLBKVRXBCJHJCRKEYNHSPXIYMWEIWQAIMTYWLOZQTBQNEXFWUOAOJ9LMBXSQQA9DCUOWOGW9JQYWXIVULMDLFVAFPYE9JGRJQLNQTOYNPK9JSB9BERTEICKVMXMLNNOCHTKVACIKEXUXZTQXGTXQMEB9SLDTOK9IFLRXYSKAFTCZVLPR9ECQVNQN9LWYRD9XLYPCKNBBCIJKCWUVGWMXMFOJLVONBKQGIISFWXAOHGUOWCM9YTCUKXKIEO9PCRBPMIPTQXQCLWMWGJHQJMAZBVHIOS9CZBAIGBX9VEKWGEQSMZVBCZNTKNJG9FEPSNLNYAVXTHLVHDTXGJCBRAXGWKBUPIGMRGFOUYFCLUXNNDDUYTFJPHTZYFUNTZXRJRTNSFYQHBSGRJPAEPFIEQJPYXKUXOUCQOXLPKRDJWAKCGWRWGJGWCOUMDDBCTVVOPQQYQOIIQIBKGMBVBPDFKNJVXVPSMZWVWXPWLUZPSXOEGTBXKJTG9ORUJUSMZXFDMMZWYRNMKGTBLXIPHVW9BSDR9LOWGSBHHBUOSZYLJJDTNJKZQWYMNCCMIVLITMFWXAWWHDCOWBTVCOGLTBILLT9KLHECWIGZVAVR99BJEMTXJRXGLXAZYHIGQOJEPMDVYAVSAPQU9JMSIXKISVTRIEI9KM9UGZKBSRLEGZU9UTJNXQRHTXZNAUDA9DFYPWEFDOOMTZICIXGIJELIUNRHSUVJJQJITOOFBFCPQSLKQKNASCTKIPGJQHVGJWETMQGUVJBISTVXNS9BLGRUWNKZQKUPSFOLO9IJQNDLXVVNZH9NRIYKJGPRJZACHECFQSQXMSGKMSTRRAFJWJJBFZCQAOBITPTMLTVLFAGBBZOYEXDFKWBJNXLUUIOSDWNLWQWUXNXOELSHBVMIMHUOPLJLNFBOQ9VPBXRRNHHEKUHNDLZ9GZZRKEXNGMCXSOPZKEUBHYNTTIHIGHDLBLSCGEQHLWKGHDKJTPXYPYNDWNSJOKKKPAUBDTVLMYBT9ITCIH9TJOPTICXTYQSFTHYX9NCZGM9TOBJLEMIVI9EPJAKKRYSDPSXLHQDWJGHJZFTWXVFVZGJKGGMYJHCW9XLFEN9UUYUUCJGEZIQVT9HKXOSBFINKEV9LWTXDDFDIG9YPWPQODFXHSGNIOBFDROPEJZZECPKOAVXVECWABNPWMQSXYSCJALKMF9HUQXAPWTEXHEJ9EQPZZKCUUBYZSNYGBL9JLIRJNZSVVYUGSVTYGUKQDQNCJFM9L9GW9XKIUGAHDUSAFW9ZURVLYGXWHRLLAKHHDRDF9BVTSNNTRJBPDAUWWFKYAHHUUFCQEEPNVPRFPDAOAZSOXKOBYXQIXER9ZH9JKUTAWAAASZ9NBXNEDSYXGARIMOEOMTENPOVGCZTPQLPAYWHUYOFQKUMMYXKTRYCESVPDUJMVDMVPCXC9GYQ9FSNDQYPDIZJZPMRBXUSKOSDDENXFWMQMELOYSFT9GAPEYRUMHRHNBNJUAIZDUOZPVIFBEMVWXDUPTQQWEQIZTVMOKGXIHACDFFRPUX9CZYLKOAVJFQMJZ9YDLSFFSQAJANI9XSGERSUIFTMQ9AZXHGJGKIGLSXBFZMQVQ9XBOHEQJAYDJBZVIZGSAAUHX9ESEDALVQKHRYCPFEAWBQVTAUBQOANWPFYHKGVQKVOYJQFIYSLRAZPLZJLDZXMYRBN9WTYTOZKHSAAIOFVBITRHRMPXNZXZYEVMKOXDMASIBMQEFBAWISR99IVOUDJFTFENZ", + Tag: "", + }})) + return []trinary.Trytes{"TRYTES"}, nil + } + fakeAPI.sendTrytes = func(trytes []trinary.Trytes, depth uint64, mwm uint64, references ...trinary.Hash) (bundle.Bundle, error) { + Expect(trytes).To(Equal([]trinary.Trytes{"TRYTES"})) + Expect(depth).To(Equal(uint64(3))) + Expect(mwm).To(Equal(uint64(9))) + Expect(references).To(Equal(([]trinary.Hash)(nil))) + return bundle.Bundle{}, nil + } + transmitter := mam.NewTransmitter(fakeAPI, seed, 9, consts.SecurityLevelLow) + + err := transmitter.SetMode(mam.ChannelModePrivate, "") + Expect(err).NotTo(HaveOccurred()) + + root, err := transmitter.Transmit("Hello!") + Expect(err).NotTo(HaveOccurred()) + Expect(root).To(Equal("YKOLXUAMTJGGIVPEWHUCSKKJWIY9PWEFABXYHAWDBTMOPKWNXOOQCKNHADSZP9SOSDFEOXPVTWUWFDQNH")) + }) + + It("Should transmit the given message to a restricted channel", func() { + fakeAPI := newFakeAPI() + fakeAPI.prepareTransfers = func(address trinary.Trytes, transfers bundle.Transfers, opts api.PrepareTransfersOptions) ([]trinary.Trytes, error) { + Expect(address).To(Equal("999999999999999999999999999999999999999999999999999999999999999999999999999999999")) + Expect(transfers).To(Equal(bundle.Transfers{bundle.Transfer{ + Address: "YTGYFFZB9IGSHRGYCXPFWCVOWVJMPSTT9BURCEVJUQVIPYRJCVTMWIA9STGLMVHDDAEVQQNW9FIADFPKXCCERTUNDD", + Value: 0, + Message: "AZBCLPZ9LZJ9GWQKKSXALILYNQLPNIKRLYXXCXRJDWPDJBLXEFJLIDQLNZNAX9URKQXWDOAQDIPTPAFVFDJCMD9QURLPTUJJEDAAMFDLOLT9LBKIPLATILWBQXMPWL9UAZEWJKOPYAHINJMGIOUEUWOXTAIMGNGJDUXSJLWWLFSRSQDZUIPTXVBXJLMFIUMKNREUQJMLQMMRZSEFTUHMPATPEOLTKYRPCS9DSIUZAAVQVGRCEBMDHYGWS9YUXLBVPHDALLTINKUJQUHUVQHXHHSTAUQNVSIAFMMKZUA9SRFQEBJC9WHCFTILSJMKQLIMUHRCNYIFZVFZQMR9WRILPXCTQUPAZSZ9NZRAAS99SMGISWNNCVHWVTUFHBKWDUOGMIHXFRM9LDEWQ9FMQA9QPCEKSOPIMIBESE9NIDBFPEBQJGHTOZTOOOIBDMNXBNFFITLLPOG99UERZFBLGEPDZXMLZWOTVGVGXXMPBICFMAKVHMQSNBWHBIRXQOTONOHKDJFGFXWITVNTLGUKSVDECQISTFCRLOSWZYPOYNKDVSVDRNU9XLBOXOCMWUGBCDWH9YETUXPZHOWECWTZEKRBUEVQMRLZWIOSNTS9QJOBXLUIXXDZZHJMLODRFTBLWFOPIGXTMMNZGUHQTKRRSVOH9EG9SBZIKZHNHSOQBKJIURFUFKROIVLSJDMAOWQTCHJXKSIAXSYLXWTSMJEXGXOFINSNKBFWITLMMIFKRP9LKIBHEBTUUZHNKSIHBGBPC9G9CA9KEZ9KAAGIL9BN9ARKYQQKNAIYNEOYVZVTKXKXMUBDJHYUEVDMFTVFQNJVRBIXMXYKGTBHLETYUUTUR9SUTQVD9GV9FHZSPBBP9LT9MCRUBYHWBKSWCVW9RGWPRSHJYLHYHUJMBTWVMUHZFLKQSDDXRJWMZAVUUS9KREIGLFQIUINCHJYPCRLSJYKLEMIJYLUMPJPUYHNEIWQVIOKIALOFUUGXQWTUVVOMCKDBHDGOYMGAVRRQ9RTRDYMBSAPR9NULXKZITOVAZOHUA9JLSLGYXWATXMM9ZWPRZUPWNGKNEORFQJHAHQXSKWBSEEIHTBDKRPRQZVGGF9U9GBPSKXKRHUQZDULHSUXSXFTLNMAZUQFSNTVLFPCLEBYZMDUJQPN9NOHXNEGBWYYUPUPHANB9CBXBLHZABCIPYQRRKYVRJVJK9WJIPXFYTLAXKNPRCJVVUGRTIYUSQSMPHUJQ9NIZCLIDKAWLEPJCJTXGXBLGHFWMDSRDIVWUTUDNUYVEZJFIURPV9GESCMPBYJTRXVWWDWCE9QTGKLAEN9Z9AESODMDKPEWRKKVKYBVPVY9MPWCMVEPSZNGRYGZOAHCIJRJZBFNZLFPXUVZVBJBF9NZOOFFXFQSMHDAYUOVTHGJTXHPUXAHHJPLOTCQGDCCRL9SKRE9FMKBC9OQWLHIONQBXTMPRTARSMDLPTVUSGSGGHYWIQ9QCOLUCQGWVQNRMLVEQZRJPSSVJGENAGD9NOGYTWVMGXEDPTIFDRFOOOZHRAZULNFATXQFGNHVOGKCJNRNROEANTZNCVALWXEI9ZDIHOIJBQBBXYPSIMPBPQJCSZUHQHSPBDJM9VLQTCDQLVZTMZJVHVFEKORFVFOVBVJBMTAFBFEIFBUKLYNMOWVVXHLSFVHAV9AVFGDHBXTPOFEKFIZNSHRPERMNARNORHVVDRGR9ZCIMSXYHHFVE9ANGCZKLQDMWQNL9JXWPVLBVGLGECEZKFPZZPWILW9QRNZQEVSMUVODVSVQGUJXSUNFDHNLLIUSJNSVQHYUDSXWBHQZWISMSJTQKAUJDEVYFMJBNYDUFSIUMQJFOENOQXKFZPEPMEPCVNZWAUE9IMENUUOWJEVMZXA9MZYLCOZHNIKSMGL9VARQ9EOLOSGSKSKWDWWWNEPQJEMNLPHRHRMHY9FXUBPIILKPPIYHSUVFAZRPTGGPGVWFKYFHNUYYBSKFBCAKFZMFHMVJYDFUVDXYTCUSOZY9RQWDYWGZCAISGVAUWPLLLBM9FGOSFYVJT9MAAPYGIWZMVHTF9ZXFLOFEKFGNRLCPIAVQQAPCVDVGSVRNU9GHJZFDTSNTMEZCJEMUXEQWHXGKQIAORTKFMHIDXFLVBXKGWAUKIZKKCMTFTTDVWEBXEWQ9YEOLWLNASIFLSUKCCN9HQLVSBVFULJWQKSVYALVDOILT9YHTUDFJHMHIVXFOLHIVTSFWEVNAFBVOUZLRZJNVMMJEBLWFYNTVGZCJAEIGNGKVNBBKAPIGTNKKNBBRTXNFUVAPHRVBIQYIOKWRNBJYC9", + Tag: "", + }})) + return []trinary.Trytes{"TRYTES"}, nil + } + fakeAPI.sendTrytes = func(trytes []trinary.Trytes, depth uint64, mwm uint64, references ...trinary.Hash) (bundle.Bundle, error) { + Expect(trytes).To(Equal([]trinary.Trytes{"TRYTES"})) + Expect(depth).To(Equal(uint64(3))) + Expect(mwm).To(Equal(uint64(9))) + Expect(references).To(Equal(([]trinary.Hash)(nil))) + return bundle.Bundle{}, nil + } + transmitter := mam.NewTransmitter(fakeAPI, seed, 9, consts.SecurityLevelLow) + + err := transmitter.SetMode(mam.ChannelModeRestricted, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") + Expect(err).NotTo(HaveOccurred()) + + root, err := transmitter.Transmit("Hello!") + Expect(err).NotTo(HaveOccurred()) + Expect(root).To(Equal("YKOLXUAMTJGGIVPEWHUCSKKJWIY9PWEFABXYHAWDBTMOPKWNXOOQCKNHADSZP9SOSDFEOXPVTWUWFDQNH")) + }) + + }) + +})