diff --git a/merkle/merkle.go b/merkle/merkle.go new file mode 100644 index 000000000..94b38e370 --- /dev/null +++ b/merkle/merkle.go @@ -0,0 +1,256 @@ +// Package merkle provides functions for creating and validating merkle trees. +package merkle + +import ( + "errors" + + . "github.com/iotaledger/iota.go/consts" + "github.com/iotaledger/iota.go/signing" + . "github.com/iotaledger/iota.go/trinary" +) + +var ( + merkleNullHash = make(Trits, HashTrinarySize) +) + +func binaryTreeSize(acc uint64, depth uint64) uint64 { + return (1 << (depth + 1)) - 1 + acc +} + +// MerkleSize computes the size of a merkle tree, e.g. its node number. +// leafCount is the number of leaves of the tree +func MerkleSize(leafCount uint64) uint64 { + var acc uint64 = 1 + + if leafCount < 2 { + return leafCount + } + + for leafCount >= 2 { + acc += leafCount + leafCount = (leafCount + 1) >> 1 + } + return acc +} + +// MerkleDepth computes the depth of a merkle tree. +// nodeCount is the number of nodes of the tree +func MerkleDepth(nodeCount uint64) (depth uint64) { + depth = 0 + + for binaryTreeSize(0, depth) < nodeCount { + depth++ + } + + return depth + 1 +} + +// merkleNodeIndexTraverse returns the node index of assign location in tree. +// The order of nodes indexes follow depth-first rule. +// acc is the number of nodes in the previous counting binary tree +// depth is the depth of the node, counting from root +// width is the width of the node, counting from left +// treeDepth is the depth of whole tree +func merkleNodeIndexTraverse(acc, depth, width, treeDepth uint64) uint64 { + if treeDepth == 0 { + return 0 + } + + var depthCursor uint64 = 1 + index := depth + acc + widthCursor := width + var widthOfLeaves uint64 = 1 << depth + + for depthCursor <= depth { + if widthCursor >= (widthOfLeaves >> depthCursor) { + // add whole binary tree size of the left side binary tree + index += ((1 << (treeDepth - depthCursor + 1)) - 1) + + // counting the node index of the subtree in which the cursor currently stays in + widthCursor = widthCursor - (widthOfLeaves >> depthCursor) + } + depthCursor++ + } + return index +} + +// MerkleNodeIndex indexes a given node in the tree. +// depth is the depth of the node, counting from root +// width is the width of the node, counting from left +// treeDepth is the depth of whole tree +func MerkleNodeIndex(depth, width, treeDepth uint64) uint64 { + return merkleNodeIndexTraverse(0, depth, width, treeDepth) +} + +// MerkleLeafIndex computes the actual site index of a leaf. +// leafIndex is the leaf index +// leafCount is the number of leaves +func MerkleLeafIndex(leafIndex, leafCount uint64) uint64 { + return leafCount - leafIndex - 1 +} + +// MerkleCreate creates a merkle tree. +// baseSize is the base size of the tree, e.g. the number of leaves +// seed is the seed used to generate addresses - Not sent over the network +// offset is the offset used to generate addresses +// security is the security used to generate addresses +// spongeFunc is the optional sponge function to use +func MerkleCreate(baseSize uint64, seed Trytes, offset uint64, security SecurityLevel, spongeFunc ...signing.SpongeFunctionCreator) (Trits, error) { + + // enforcing the tree to be perfect by checking if the base size (number of leaves) is a power of two + if (baseSize != 0) && (baseSize&(baseSize-1)) != 0 { + return nil, errors.New("Base size of the merkle tree should be a power of 2") + } + + treeMerkleSize := MerkleSize(baseSize) + tree := make(Trits, treeMerkleSize*HashTrinarySize) + + h := signing.GetSpongeFunc(spongeFunc) + + td := MerkleDepth(treeMerkleSize) - 1 + + // create base addresses + for leafIndex := uint64(0); leafIndex < baseSize; leafIndex++ { + subSeed, err := signing.Subseed(seed, offset+MerkleLeafIndex(leafIndex, baseSize), spongeFunc...) + if err != nil { + return nil, err + } + + key, err := signing.Key(subSeed, security, spongeFunc...) + if err != nil { + return nil, err + } + + keyDigests, err := signing.Digests(key, spongeFunc...) + if err != nil { + return nil, err + } + + address, err := signing.Address(keyDigests, spongeFunc...) + if err != nil { + return nil, err + } + + treeIdx := HashTrinarySize * MerkleNodeIndex(td, leafIndex, td) + copy(tree[treeIdx:treeIdx+HashTrinarySize], address) + } + + // hash tree + curSize := baseSize + for depth := td; depth > 0; depth-- { + for width := uint64(0); width < curSize; width += 2 { + + merkleNodeIdxLeft := MerkleNodeIndex(depth, width, td) * HashTrinarySize + merkleNodeIdxRight := MerkleNodeIndex(depth, width+1, td) * HashTrinarySize + + if width < curSize-1 { + // if right hash exists, absorb right hash then left hash + h.Absorb(tree[merkleNodeIdxRight : merkleNodeIdxRight+HashTrinarySize]) + h.Absorb(tree[merkleNodeIdxLeft : merkleNodeIdxLeft+HashTrinarySize]) + } else { + // else, absorb the remaining hash then a null hash + h.Absorb(tree[merkleNodeIdxLeft : merkleNodeIdxLeft+HashTrinarySize]) + h.Absorb(merkleNullHash) + } + + // squeeze the result in the parent node + trits, err := h.Squeeze(HashTrinarySize) + if err != nil { + return nil, err + } + + parentIdx := MerkleNodeIndex(depth-1, width/2, td) * HashTrinarySize + copy(tree[parentIdx:parentIdx+HashTrinarySize], trits) + h.Reset() + } + curSize = (curSize + 1) >> 1 + } + + return tree, nil +} + +// MerkleBranch creates the merkle branch to generate back root from index. +// tree is the merkle tree - Must be allocated +// siblings is the siblings of the indexed node - Must be allocated +// treeLength is the length of the tree +// treeDepth is the depth of the tree +// leafIndex is the index of the leaf to start the branch from +// leafCount is the number of leaves of the tree +func MerkleBranch(tree Trits, siblings Trits, treeLength, treeDepth, leafIndex, leafCount uint64) (Trits, error) { + + if tree == nil { + return nil, errors.New("Null tree") + } + + if siblings == nil { + return nil, errors.New("Null sibling") + } + + if HashTrinarySize*MerkleNodeIndex(treeDepth-1, leafIndex, treeDepth-1) >= treeLength { + return nil, errors.New("Leaf index out of bounds") + } + + if treeDepth > MerkleDepth(treeLength/HashTrinarySize) { + return nil, errors.New("Depth out of bounds") + } + + var siblingIndex, siteIndex uint64 + + siblingIndex = MerkleLeafIndex(leafIndex, leafCount) + + var i int64 = 0 + for depthIndex := treeDepth - 1; depthIndex > 0; depthIndex-- { + + if (siblingIndex & 1) != 0 { + siblingIndex-- + } else { + siblingIndex++ + } + + siteIndex = HashTrinarySize * MerkleNodeIndex(depthIndex, siblingIndex, treeDepth-1) + if siteIndex >= treeLength { + // if depth width is not even, copy a null hash + copy(siblings[i*HashTrinarySize:(i+1)*HashTrinarySize], merkleNullHash) + } else { + // else copy a sibling + copy(siblings[i*HashTrinarySize:(i+1)*HashTrinarySize], tree[siteIndex:siteIndex+HashTrinarySize]) + } + + siblingIndex >>= 1 + i++ + } + return siblings, nil +} + +// MerkleRoot generates a merkle root from a hash and his siblings, +// hash is the hash +// siblings is the hash siblings +// siblingsNumber is the number of siblings +// leafIndex is the node index of the hash +// spongeFunc is the optional sponge function to use +func MerkleRoot(hash Trits, siblings Trits, siblingsNumber uint64, leafIndex uint64, spongeFunc ...signing.SpongeFunctionCreator) (Trits, error) { + h := signing.GetSpongeFunc(spongeFunc) + + var j uint64 = 1 + var err error + + for i := uint64(0); i < siblingsNumber; i++ { + siblingsIdx := i * HashTrinarySize + + if (leafIndex & j) != 0 { + // if index is a right node, absorb a sibling (left) then the hash + err = h.Absorb(siblings[siblingsIdx : siblingsIdx+HashTrinarySize]) + err = h.Absorb(hash) + } else { + // if index is a left node, absorb the hash then a sibling (right) + err = h.Absorb(hash) + err = h.Absorb(siblings[siblingsIdx : siblingsIdx+HashTrinarySize]) + } + + hash, err = h.Squeeze(HashTrinarySize) + h.Reset() + + j <<= 1 + } + return hash, err +} diff --git a/merkle/merkle_suite_test.go b/merkle/merkle_suite_test.go new file mode 100644 index 000000000..3c57a4411 --- /dev/null +++ b/merkle/merkle_suite_test.go @@ -0,0 +1,13 @@ +package merkle_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestMerkle(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Merkle Suite") +} diff --git a/merkle/merkle_test.go b/merkle/merkle_test.go new file mode 100644 index 000000000..fcfd769e4 --- /dev/null +++ b/merkle/merkle_test.go @@ -0,0 +1,53 @@ +package merkle_test + +import ( + . "github.com/iotaledger/iota.go/consts" + . "github.com/iotaledger/iota.go/merkle" + . "github.com/iotaledger/iota.go/signing" + . "github.com/iotaledger/iota.go/trinary" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const ( + leafCount = 32 +) + +var _ = Describe("Merkle", func() { + + Context("Merkle()", func() { + It("Merkle", func() { + var size, depth uint64 + var err error + var merkleTree, siblings, hash Trits + + size = MerkleSize(leafCount) + Expect(size).To(Equal(uint64(63))) + + depth = MerkleDepth(size) + Expect(depth).To(Equal(uint64(6))) + + merkleTree = make(Trits, size*HashTrinarySize) + siblings = make(Trits, (depth-1)*HashTrinarySize) + hash = make(Trits, HashTrinarySize) + + merkleTree, err = MerkleCreate(leafCount, "ABCDEFGHIJKLMNOPQRSTUVWXYZ9ABCDEFGHIJKLMNOPQRSTUVWXYZ9ABCDEFGHIJKLMNOPQRSTUVWXYZ9", 7, SecurityLevel(2), NewCurlP81) + Expect(err).ToNot(HaveOccurred()) + + for i := uint64(0); i < leafCount; i++ { + siblings, err = MerkleBranch(merkleTree, siblings, size*HashTrinarySize, depth, i, leafCount) + Expect(err).ToNot(HaveOccurred()) + + merkleTreeIdx := MerkleNodeIndex(depth-1, MerkleLeafIndex(i, leafCount), depth-1) * HashTrinarySize + + copy(hash, merkleTree[merkleTreeIdx:merkleTreeIdx+HashTrinarySize]) + + hash, err = MerkleRoot(hash, siblings, depth-1, i, NewCurlP81) + Expect(err).ToNot(HaveOccurred()) + + Expect(merkleTree[:HashTrinarySize]).To(Equal(hash)) + } + }) + }) +}) diff --git a/signing/.examples/signing_examples_test.go b/signing/.examples/signing_examples_test.go index 33a8f1f48..952cc766b 100644 --- a/signing/.examples/signing_examples_test.go +++ b/signing/.examples/signing_examples_test.go @@ -2,13 +2,17 @@ package signing_examples_test import ( "fmt" + "github.com/iotaledger/iota.go/consts" "github.com/iotaledger/iota.go/signing" "github.com/iotaledger/iota.go/trinary" ) -// o: SpongeFunction, The SpongeFunction interface using Curl. -func ExampleNewCurl() {} +// o: SpongeFunction, The SpongeFunction interface using CurlP27. +func ExampleNewCurlP27() {} + +// o: SpongeFunction, The SpongeFunction interface using CurlP81. +func ExampleNewCurlP81() {} // o: SpongeFunction, The SpongeFunction interface using Kerl. func ExampleNewKerl() {} diff --git a/signing/signing.go b/signing/signing.go index db6c5259b..3398083b2 100644 --- a/signing/signing.go +++ b/signing/signing.go @@ -20,18 +20,23 @@ type SpongeFunction interface { Reset() } -// New returns a new Curl. -func NewCurl() SpongeFunction { - return curl.NewCurl() +// NewCurlP27 returns a new CurlP27. +func NewCurlP27() SpongeFunction { + return curl.NewCurl(curl.CurlP27) } -// New returns a new Kerl. +// NewCurlP81 returns a new CurlP81. +func NewCurlP81() SpongeFunction { + return curl.NewCurl(curl.CurlP81) +} + +// NewKerl returns a new Kerl. func NewKerl() SpongeFunction { return kerl.NewKerl() } -// getSpongeFunc checks if a hash function was given, otherwise uses Kerl. -func getSpongeFunc(spongeFuncCreator []SpongeFunctionCreator) SpongeFunction { +// GetSpongeFunc checks if a hash function was given, otherwise uses Kerl. +func GetSpongeFunc(spongeFuncCreator []SpongeFunctionCreator) SpongeFunction { if len(spongeFuncCreator) > 0 { return spongeFuncCreator[0]() } @@ -54,7 +59,7 @@ func Subseed(seed Trytes, index uint64, spongeFunc ...SpongeFunctionCreator) (Tr incrementedSeed := AddTrits(trits, IntToTrits(int64(index))) - h := getSpongeFunc(spongeFunc) + h := GetSpongeFunc(spongeFunc) err = h.Absorb(incrementedSeed) if err != nil { return nil, err @@ -70,7 +75,7 @@ func Subseed(seed Trytes, index uint64, spongeFunc ...SpongeFunctionCreator) (Tr // Key computes a new private key from the given subseed using the given security level. // Optionally takes the SpongeFunction to use. Default is Kerl. func Key(subseed Trits, securityLevel SecurityLevel, spongeFunc ...SpongeFunctionCreator) (Trits, error) { - h := getSpongeFunc(spongeFunc) + h := GetSpongeFunc(spongeFunc) if err := h.Absorb(subseed); err != nil { return nil, err } @@ -108,7 +113,7 @@ func Digests(key Trits, spongeFunc ...SpongeFunctionCreator) (Trits, error) { // hash each segment 26 times for k := 0; k < KeySegmentHashRounds; k++ { - h := getSpongeFunc(spongeFunc) + h := GetSpongeFunc(spongeFunc) h.Absorb(buf) buf, err = h.Squeeze(HashTrinarySize) if err != nil { @@ -122,7 +127,7 @@ func Digests(key Trits, spongeFunc ...SpongeFunctionCreator) (Trits, error) { } // hash the key fragment (which now consists of hashed segments) - h := getSpongeFunc(spongeFunc) + h := GetSpongeFunc(spongeFunc) if err := h.Absorb(keyFragment); err != nil { return nil, err } @@ -142,7 +147,7 @@ func Digests(key Trits, spongeFunc ...SpongeFunctionCreator) (Trits, error) { // Address generates the address trits from the given digests. // Optionally takes the SpongeFunction to use. Default is Kerl. func Address(digests Trits, spongeFunc ...SpongeFunctionCreator) (Trits, error) { - h := getSpongeFunc(spongeFunc) + h := GetSpongeFunc(spongeFunc) if err := h.Absorb(digests); err != nil { return nil, err } @@ -189,7 +194,7 @@ func SignatureFragment(normalizedBundleHashFragment Trits, keyFragment Trits, sp sigFrag := make(Trits, len(keyFragment)) copy(sigFrag, keyFragment) - h := getSpongeFunc(spongeFunc) + h := GetSpongeFunc(spongeFunc) for i := 0; i < KeySegmentsPerFragment; i++ { hash := sigFrag[i*HashTrinarySize : (i+1)*HashTrinarySize] @@ -218,14 +223,14 @@ func SignatureFragment(normalizedBundleHashFragment Trits, keyFragment Trits, sp // Digest computes the digest derived from the signature fragment and normalized bundle hash. // Optionally takes the SpongeFunction to use. Default is Kerl. func Digest(normalizedBundleHashFragment []int8, signatureFragment Trits, spongeFunc ...SpongeFunctionCreator) (Trits, error) { - h := getSpongeFunc(spongeFunc) + h := GetSpongeFunc(spongeFunc) buf := make(Trits, HashTrinarySize) for i := 0; i < KeySegmentsPerFragment; i++ { copy(buf, signatureFragment[i*HashTrinarySize:(i+1)*HashTrinarySize]) for j := normalizedBundleHashFragment[i] + MaxTryteValue; j > 0; j-- { - hh := getSpongeFunc(spongeFunc) + hh := GetSpongeFunc(spongeFunc) err := hh.Absorb(buf) if err != nil { return nil, err diff --git a/signing/signing_milestone_test.go b/signing/signing_milestone_test.go new file mode 100644 index 000000000..c7ab7b07a --- /dev/null +++ b/signing/signing_milestone_test.go @@ -0,0 +1,48 @@ +package signing_test + +import ( + "github.com/iotaledger/iota.go/consts" + "github.com/iotaledger/iota.go/merkle" + . "github.com/iotaledger/iota.go/signing" + . "github.com/iotaledger/iota.go/transaction" + . "github.com/iotaledger/iota.go/trinary" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Signing", func() { + const cooAddress = "KPWCHICGJZXKE9GSUDXZYUAPLHAKAHYHDXNPHENTERYMMBQOPSQIDENXKLKCEYCPVTZQLEEJVYJZV9BWU" + + const NUMBER_OF_KEYS_IN_MILESTONE uint64 = 20 + const milestoneIndex uint64 = 881506 + const trytesMsTx1 = "TRZLNPUJJWZLQ9O9ORWPFEEROJVUEGJCQ99FAHQE99PFRIAWVUETRMWNNWFWJBVFOHRIXRZUEUOPXYFZCBJAUXAVHNFAYAK9UMFLIODHGSGFUGPGHQMSNFKLIWTEHHWLT9GLYHWWNRZUQNTVYFKKGBCTICTCVEJDUVJZYHBYQGVMHOUJXNVQIFLEDXXZSILGCZRQAMIXFSBFBZBFIHCOAUZCETRULDMSRKMSCG9LRIFVVNQHAOQCSWA9BOKN9XVXUOOYPCGAHFWZQEHTOXTUZPUUWOAH9CIVGNBGISCQPWRKMZZSCIQAIAVXIYTMWRPTLCQZGRAAHUJYNCPBQJK9VLOESU99FMWETKUCIBTZPCAVZJQXOOAFVICJZFGKLCSXTAOUOHIZTJBMUHSSTNCWQKKQ9WUBELSNYXOMHKPNVUYZOGFFWHVSORIGYQYDPKDY9HHVWYT9CZZXAHCPYQNMREEAIXDTKRJQXTOPY9FZUTVNPKVMFHSOIGWRZO9JDGKDJWLD9BNZIPCBYSDQURHOHCZT9UJGRETWQUOXAZQQQJCHID9AJJBSZISKNFLJQECBQNJGTOGCFCVRGSYCYBREJXFETKIHQOMGGHVFXAXYBNPHXJATKSABYMQERL99TNEPLSOIQWPPPFDRXRGEXAPZRDYZBXJTLTGTTICSPUEFOFRFJENJCDYZQVGMEMMWNDJWICN99BKFRX99ZZTKHDAODNTYAHJAQAOYASXDX9RSJHDXYFMMJSVZIBNWWEEXYRPSPRHWSBAENRYLXC9BDQCMGIESRNCPGBLTVZTY9EYVXRIKBEQY9JSSTBGCSKRNTLLJY9R9UDNWMHVCYKJCVEJAEBYMRODQWDCZQ9IOEPEVOGNOCNUWDCUEWNFUSLGYMJSC9ZUPXCGYT9ZIASDJLQJLYRIGFDZZFYCUAJTTYLQJVJWYECPVWEYGWKWIHRYZDFICROGUBAORLBQHCL9DIBQRV9NSYXWSAHJKMSXIFPOLVJAPPYV9SGLSPIAHQWXRRFXG9DMEMRWGHOVGLSCSBRZTZVIKMTERQCS9GVFJGTCVEIVLBWWGBO9XKOJMDPMUXMYYK9R9BSHLGRBOMWUAWDXEQEAWBJKXVLBTL9OBYOWFHSIXOPTQVFVIGFFJFXJHTJUQRGIXKBEXORMGCXMLPOCYFJWKU9SDBBGKOBGAPSWGLZFOGXSPQRLUGRDUDIYOHEXLGYSWZRPYDIUYZJ9IELS9USHGIROHBHHIFX9AMKTZXOGH9BRKSKOYTHVWLV9JZM9QPKJZZWVKOCEZ9PDMPFUZGPBLTAQKKWJVFBXGQWGBKZMIHLYBKO9KGCLXVNPWIKPIZRFPPEYZBLRWDTHVRRNLLDB9NOAAGEHTSNZRBYTQDRMIIVTQPFUCGUGTT9HMHHANWHOQ9QEYP9TJQQAPPRIUOBNTKPX9V9EFDLVEXBCFCJHJORVEMUSIZIZLKCXFRWVW9AFCYJCSIHZIVVZUNWDOGCCUMS9NMVEQJKTSTXDOMXUFKRBKCTIAZGMRUVKUG9ACEHGZ9NODFHVCORHETF9RHUTRAZYDQWXJIFTAAAGYKRBEUIUGSVINCUOMVLZS9JKXRIXSAMOD9NC9IRGKXTGKMKECQYOZKBRWYALEWTFFGQU9OANXNSDRGSGLJNURYQPEDGXATPPSLSTGTQTBQOKQGJOTCMPTD9UMWCHPLDOEFZFMIQJLDTWHAZJYBOFBAFKWMIOHSEMWOKENWJTICLUDWPQMPHLQURZVYNGYADPLIJP9UWLTEWA9BIORPPEFRQMAZCVHBWXJCOLODUGKUIUNUWSHUDDFLERKCRDMUYCUASWOGXCXRQPUJTXGDS9OBVFRFYXFKNRMAUPCZKD9DGVXAPBO99ZWCTGWNQKMGXXFVXHGDNTOAMEWWKCDNWDGKKVQ9JPPZZXBTEFLBAFKHIM9VOFDLJOHRIWISTWQCMIAIZD9WJUDACSOZYMUPWMLRSRDRBMKKJAZ9LWJREVOPLRGSLMITHLGUQBBKY9XPWQZGIKDKOEYSOQQGCUAYWRBTRANTAMESRYK9ZAMAXDGIKSRCBSZDCLWCSSHAJSIWPXRGPBIVXGNZWBWPZWPWX9MHDLPWYNCCPEPVRDUXHDUNDWWWDATITVQFMRGRWJQAKKTYDMPYGAKYHHOTKLVZKVHDWIIPPHCVB9TGEQKPWCHICGJZXKE9GSUDXZYUAPLHAKAHYHDXNPHENTERYMMBQOPSQIDENXKLKCEYCPVTZQLEEJVYJZV9BWU999999999999999999999999999JEURB9999999999999999999999EJVBMZD99999999999A99999999FCCIAAKYIYCDFNJZJDCPKND9ONMKHVC9UIXGRIILSWUODUKPSEBS9RREJGYAFLGQRYWYFR9UIMHVEUKJAMUPBNTNJTQNBGLLKJWKVJHEDDWBDHBUFKXEQALOZX9PTXAPPGNYAFJPPFDOM9AFBCWUSQTUSKGRLZ9999RRXBJQIBWGPCW9SFWVZTBJGJEOKRILWSLBBVNMZJHQEJIHKRGMMIZHTJBLMHKGS9SKNQCVBCVTOUZ9999999999999999999999999999999999999999999999999999999999ZHWTS9ITKCARXAAITBPHYPXQOM9" + const trytesMsTx2 = "SLO9VVX9SEIUNUQSOJKRSBHHXAEGJUNVZGWTHIQQNYQO9MCCJZDOVTGYBLEFYVREZGTWVVROULNMTFYILMNCDLKBQZVFNXDNYUXDTKS9YIUUH9URAZBQ9AGAJ9PXXUYAIMPZMYKRGUWJFPWISLYOFAH9PYYZAZOYIRFMMEIXWVFACOWSRDZLTRRPXLUHNBDCQEYNWVVNOUJTJPVOMFVGPO9TRJ9PHVQWXMKLMSUPK9TITDWHMVRDHCERIV9QPJTTFUJXPURR9KF9HMNCIYQJLJPVJ99VVIBNMNZYIUWWWTGPGDNPXMBWNBJQRFNNFDKTYDUABVHVIHHBZAVKMMBNPOVJQGSKVHNTKWONIGLZKDPSB99YTDUJCUGUNLTQYPWSIXY9JYACDZMFLTGKZILVTEODUUTMVLRSFSOIREJSDAWEFKDVASBZLJKEOVCGEFCZSBLGRYOSCGGKFCDFNXEEJJKBAPEAYNEXTDUSSMCQZJUTUOVLBLCCSRCR99NRUQCKYWQGFJXMKASRAYX9SDJXALDZ9TIZB9DADCK9TX9SFXMQCIHDCGJSCIHWAC9XTZULORQJ9XVHMEFNLUHMKFCKDHWVEOSJGKPTSACPUWAVLPBEVCXVDHYVBYQWHOZCMRFXCRG9PDJN9FIUMCTP9JNIJUSGQVYNWDVQRSKVRTAPDAXTYBABSIBISYWFUOLMZQXNDEHTJBCYWBJ9QTAFWBT9GOEIVXNGTHDB9WOWKEGX9MCLZV9DFLCELSRLZKQXNMCVTWRJZTAOERTTEHTDOOZVSVHEBBSJDWMBLWSVMUAGSJXHNGJFLC9UEDGAYEZLVAHIALWRSXNKORWPXUAXCYGVBSZVYKTTPATJRFBNTWW9IEKVONATJOCAERWFNCGWNHXAL9ASMIFCYFFXCPRFLQCGQVNUGBMEXIQTFMGCMPAOKRZEYMYWFDRRVELYNDLPFGZDWGGQDNZIBLYNGOEKWMLRTFGNWHVMRAQVWTAMFXHUGMASKPLPBKESNCRBEGLLVJDQLIQXRNRYCOHRVVTDEEO9ESIIINKOTHQ99GMRCHXAOCAOQLMSUPEBNEJANPCEUXTWHOEKQRSTZTYZBLAHSBOGPMPGQEZZPUKLBSMKFRGKCWYGLRQDITOBWQOHRQFZJHZEPXD9EOXRWSR9YRLKLAAJGHHIRCPXYIP9BJ9WU9DYSFQMVEDTSGAONNPCKECNDVQELBPZXVJOPJARGPPZWZ9XPSPFFJSII9PUYOTL9BLGZGXUP9SRGXZCMXOZV9MZLPZRSDHANXEMHDHMZVLXDLYSPUFZSIGRWEDN9LOOIXGHJDZWJQCPNQCDLMV9DP9HGYRBJODHICIUNOMXXFVLUUYZRMSMOMATVHOSZCPBJNDWYDQNUKJSZDEPHKGPCWINQJAMRWTMLDBADVGJFGBAAEHTORSR9HCTWEDBULTRJ9QKJTLSNXXU9FRCINCAATXRPSKRTGOWPMPWMKNGQETVXNDKJFCKIEQB9CBPEHCLATHLOQMHPGUNKMHMDMVTVLNYJCNGEAJRBXSFUUKOAHKAPHTIIDMYEAZYGKFVJBSDJPVVOQLRWCQEDECG99RARUYJYXIMAEVFRWJ9SJFUSNN9KZGR999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999EJVBMZD99A99999999A99999999FCCIAAKYIYCDFNJZJDCPKND9ONMKHVC9UIXGRIILSWUODUKPSEBS9RREJGYAFLGQRYWYFR9UIMHVEUKJARRXBJQIBWGPCW9SFWVZTBJGJEOKRILWSLBBVNMZJHQEJIHKRGMMIZHTJBLMHKGS9SKNQCVBCVTOUZ9999RRXBJQIBWGPCW9SFWVZTBJGJEOKRILWSLBBVNMZJHQEJIHKRGMMIZHTJBLMHKGS9SKNQCVBCVTOUZ9999999999999999999999999999999999999999999999999999999999ATTPAVULAKMRCMHUYODBIJZLCDO" + + Context("MerkleRoot()", func() { + It("Checks milestone signature", func() { + mxTx1, err := AsTransactionObject(trytesMsTx1) + Expect(err).ToNot(HaveOccurred()) + + mxTx1SignatureMessageFragmentTrits := MustTrytesToTrits(mxTx1.SignatureMessageFragment)[:consts.SignatureMessageFragmentTrinarySize] + mxTx2Trits := MustTrytesToTrits(trytesMsTx2)[:consts.TransactionTrinarySize] + + normalized := NormalizedBundleHash(mxTx1.TrunkTransaction)[:27] + digests, err := Digest(normalized, mxTx1SignatureMessageFragmentTrits, NewCurlP27) + Expect(err).ToNot(HaveOccurred()) + address, err := Address(digests, NewCurlP27) + Expect(err).ToNot(HaveOccurred()) + + merkleRoot, err := merkle.MerkleRoot( + address, + mxTx2Trits, + NUMBER_OF_KEYS_IN_MILESTONE, + milestoneIndex, + NewCurlP27, + ) + Expect(err).ToNot(HaveOccurred()) + + merkleAddress := MustTritsToTrytes(merkleRoot) + Expect(merkleAddress).To(Equal(cooAddress)) + }) + }) +})