Skip to content

Commit

Permalink
Changed how the TransactionID is computed
Browse files Browse the repository at this point in the history
Added a way to generate OutputID proofs from a given transaction and output index and a way to validate them given the output.
  • Loading branch information
alexsporn committed Oct 10, 2023
1 parent 3d51abf commit 672944a
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 25 deletions.
5 changes: 5 additions & 0 deletions api_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/hive.go/serializer/v2"
"github.com/iotaledger/hive.go/serializer/v2/serix"
"github.com/iotaledger/iota.go/v4/merklehasher"
)

const (
Expand Down Expand Up @@ -629,6 +630,10 @@ func V3API(protoParams ProtocolParameters) API {
))
}

{
merklehasher.RegisterSerixRules[*APIByter[TxEssenceOutput]](api)
}

return v3
}

Expand Down
16 changes: 9 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ go 1.21
require (
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/ethereum/go-ethereum v1.12.2
github.com/iotaledger/hive.go/constraints v0.0.0-20231005142627-86973b2edb3b
github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231005142627-86973b2edb3b
github.com/iotaledger/hive.go/crypto v0.0.0-20231005142627-86973b2edb3b
github.com/iotaledger/hive.go/ierrors v0.0.0-20231005142627-86973b2edb3b
github.com/iotaledger/hive.go/lo v0.0.0-20230929122509-67f34bfed40d
github.com/iotaledger/hive.go/constraints v0.0.0-20231010113711-a208cf7170ab
github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231010113711-a208cf7170ab
github.com/iotaledger/hive.go/crypto v0.0.0-20231010113711-a208cf7170ab
github.com/iotaledger/hive.go/ierrors v0.0.0-20231010113711-a208cf7170ab
github.com/iotaledger/hive.go/lo v0.0.0-20231010113711-a208cf7170ab
github.com/iotaledger/hive.go/runtime v0.0.0-20230929122509-67f34bfed40d
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231005142627-86973b2edb3b
github.com/iotaledger/hive.go/stringify v0.0.0-20231005142627-86973b2edb3b
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231010113711-a208cf7170ab
github.com/iotaledger/hive.go/stringify v0.0.0-20231010113711-a208cf7170ab
github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c
github.com/samber/lo v1.38.1
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.12.0
gopkg.in/h2non/gock.v1 v1.1.2
Expand All @@ -31,6 +32,7 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
Expand Down
32 changes: 18 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,22 @@ github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o
github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw=
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
github.com/iotaledger/hive.go/constraints v0.0.0-20231005142627-86973b2edb3b h1:8FdKoB755PjCL1aHTi+2rPt9lCUS4B2wg2fNKykw5dc=
github.com/iotaledger/hive.go/constraints v0.0.0-20231005142627-86973b2edb3b/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s=
github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231005142627-86973b2edb3b h1:gQ+Wqg8h/LRpgX3CsmaOZYdMIBVL4fAPIrZbmA6Wn3Q=
github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231005142627-86973b2edb3b/go.mod h1:jn3TNmiNRIiQm/rS4VD+7wFHI2+UXABHvCA3PbQxBqI=
github.com/iotaledger/hive.go/crypto v0.0.0-20231005142627-86973b2edb3b h1:F91AhJfeVN/XdvzUObUXa5mA38VezBZ9GHhdfQw4fVg=
github.com/iotaledger/hive.go/crypto v0.0.0-20231005142627-86973b2edb3b/go.mod h1:jP68na941d9uq7RtnA8aQ/FtIGRGz/51cU4uXrInQFU=
github.com/iotaledger/hive.go/ierrors v0.0.0-20231005142627-86973b2edb3b h1:D07ocbgOyj2d/AGjrIuAxIaQC5SQDndiKp9UaaQwLas=
github.com/iotaledger/hive.go/ierrors v0.0.0-20231005142627-86973b2edb3b/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8=
github.com/iotaledger/hive.go/lo v0.0.0-20230929122509-67f34bfed40d h1:qNmg1DUvge8zPvygQEoulQjLG7gFzWKqPMJ3r7ZESm0=
github.com/iotaledger/hive.go/lo v0.0.0-20230929122509-67f34bfed40d/go.mod h1:4oKCdMEhHMLCudBz79kuvJmgSY/DhfVePNIyJhew/80=
github.com/iotaledger/hive.go/constraints v0.0.0-20231010113711-a208cf7170ab h1:2g7lZLh5lsYjubQiYSDSxNqUVtEtUeIZoF7AOtiBSQ4=
github.com/iotaledger/hive.go/constraints v0.0.0-20231010113711-a208cf7170ab/go.mod h1:dOBOM2s4se3HcWefPe8sQLUalGXJ8yVXw58oK8jke3s=
github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231010113711-a208cf7170ab h1:joeVmUK6MMExsbAJbugVGgn0KtYX2JM1pWgn2uFsLD4=
github.com/iotaledger/hive.go/core v1.0.0-rc.3.0.20231010113711-a208cf7170ab/go.mod h1:jn3TNmiNRIiQm/rS4VD+7wFHI2+UXABHvCA3PbQxBqI=
github.com/iotaledger/hive.go/crypto v0.0.0-20231010113711-a208cf7170ab h1:vEjCHMTSAP42VU1DtZE+O42z7QPC8tI7gG+xpnpkQjE=
github.com/iotaledger/hive.go/crypto v0.0.0-20231010113711-a208cf7170ab/go.mod h1:jP68na941d9uq7RtnA8aQ/FtIGRGz/51cU4uXrInQFU=
github.com/iotaledger/hive.go/ierrors v0.0.0-20231010113711-a208cf7170ab h1:KqyZkt5bYYAo5yO5gLkjGa4s9fcEuse5A7KT9d8MIKg=
github.com/iotaledger/hive.go/ierrors v0.0.0-20231010113711-a208cf7170ab/go.mod h1:HcE8B5lP96enc/OALTb2/rIIi+yOLouRoHOKRclKmC8=
github.com/iotaledger/hive.go/lo v0.0.0-20231010113711-a208cf7170ab h1:m0p/CepaNrSUAtPHy+HIdzgXLwKIGUSr2AHqRAe0Ru0=
github.com/iotaledger/hive.go/lo v0.0.0-20231010113711-a208cf7170ab/go.mod h1:4oKCdMEhHMLCudBz79kuvJmgSY/DhfVePNIyJhew/80=
github.com/iotaledger/hive.go/runtime v0.0.0-20230929122509-67f34bfed40d h1:mn2Gax95UuUpuzEi4osLk+1IBjv5q56LwcxF/lAxk38=
github.com/iotaledger/hive.go/runtime v0.0.0-20230929122509-67f34bfed40d/go.mod h1:fXVyQ1MAwxe/EmjAnG8WcQqbzGk9EW/FsJ/n16H/f/w=
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231005142627-86973b2edb3b h1:6anjtWbaCszD5h43psnE8lsgIM0etpjr62ZlN59t0H8=
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231005142627-86973b2edb3b/go.mod h1:IJgaaxbgKCsNat18jlJJEAxCY2oVYR3F30B+M4vJ89I=
github.com/iotaledger/hive.go/stringify v0.0.0-20231005142627-86973b2edb3b h1:cc9VsDzLxPAaC8fj96EA1bJxbmrEZndJV+3SmG+HHOs=
github.com/iotaledger/hive.go/stringify v0.0.0-20231005142627-86973b2edb3b/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs=
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231010113711-a208cf7170ab h1:7hB97d+zP0vWIDZlT0jsnaY2nXnHxQlNYqZ9qs1xPYE=
github.com/iotaledger/hive.go/serializer/v2 v2.0.0-rc.1.0.20231010113711-a208cf7170ab/go.mod h1:IJgaaxbgKCsNat18jlJJEAxCY2oVYR3F30B+M4vJ89I=
github.com/iotaledger/hive.go/stringify v0.0.0-20231010113711-a208cf7170ab h1:udy0r5O3nlSRYOcXJeIieVSNQt4r0IrXzvn+OeUV5/I=
github.com/iotaledger/hive.go/stringify v0.0.0-20231010113711-a208cf7170ab/go.mod h1:FTo/UWzNYgnQ082GI9QVM9HFDERqf9rw9RivNpqrnTs=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand All @@ -53,10 +53,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU=
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
Expand Down
118 changes: 118 additions & 0 deletions output_id_proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package iotago

import (
"context"
"crypto"

"github.com/iotaledger/hive.go/ierrors"
"github.com/iotaledger/hive.go/lo"
"github.com/iotaledger/iota.go/v4/merklehasher"
)

type OutputIDProof struct {
API API
Slot SlotIndex `serix:"0,mapKey=slot"`
OutputIndex uint16 `serix:"1,mapKey=outputIndex"`
TransactionCommitment Identifier `serix:"2,mapKey=transactionCommitment"`
OutputCommitmentProof *merklehasher.Proof[*APIByter[TxEssenceOutput]] `serix:"3,mapKey=outputCommitmentProof"`
}

func OutputIDProofForOutputAtIndex(tx *Transaction, index uint16) (*OutputIDProof, error) {
if tx.API == nil {
return nil, ierrors.New("API not set")
}

if int(index) >= len(tx.Outputs) {
return nil, ierrors.Errorf("index %d out of bounds len=%d", index, len(tx.Outputs))
}

outputHasher := merklehasher.NewHasher[*APIByter[TxEssenceOutput]](crypto.BLAKE2b_256)

Check failure on line 29 in output_id_proof.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] output_id_proof.go#L29

BLAKE2b_256 contains underscore. You should use mixedCap or MixedCap. (nosnakecase)
Raw output
output_id_proof.go:29:76: BLAKE2b_256 contains underscore. You should use mixedCap or MixedCap. (nosnakecase)
	outputHasher := merklehasher.NewHasher[*APIByter[TxEssenceOutput]](crypto.BLAKE2b_256)
	                                                                          ^
wrappedOutputs := lo.Map(tx.Outputs, APIByterFactory[TxEssenceOutput](tx.API))

proof, err := outputHasher.ComputeProofForIndex(wrappedOutputs, int(index))
if err != nil {
return nil, ierrors.Wrapf(err, "failed to compute proof for index %d", index)
}

transactionCommitment, err := tx.TransactionCommitment()
if err != nil {
return nil, err
}

return &OutputIDProof{
API: tx.API,
Slot: tx.CreationSlot,
OutputIndex: index,
TransactionCommitment: transactionCommitment,
OutputCommitmentProof: proof,
}, nil
}

func OutputIDProofFromBytes(api API) func([]byte) (*OutputIDProof, int, error) {
return func(b []byte) (proof *OutputIDProof, consumedBytes int, err error) {
proof = new(OutputIDProof)
consumedBytes, err = api.Decode(b, proof)

return proof, consumedBytes, err
}
}

func (p *OutputIDProof) Bytes() ([]byte, error) {
return p.API.Encode(p)
}

func (p *OutputIDProof) SetDeserializationContext(ctx context.Context) {
p.API = APIFromContext(ctx)
}

func (p *OutputIDProof) OutputID(output Output) (OutputID, error) {
if p.API == nil {
return EmptyOutputID, ierrors.New("API not set")
}

outputHasher := merklehasher.NewHasher[*APIByter[TxEssenceOutput]](crypto.BLAKE2b_256)

Check failure on line 73 in output_id_proof.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] output_id_proof.go#L73

BLAKE2b_256 contains underscore. You should use mixedCap or MixedCap. (nosnakecase)
Raw output
output_id_proof.go:73:76: BLAKE2b_256 contains underscore. You should use mixedCap or MixedCap. (nosnakecase)
	outputHasher := merklehasher.NewHasher[*APIByter[TxEssenceOutput]](crypto.BLAKE2b_256)
	                                                                          ^

contains, err := p.OutputCommitmentProof.ContainsValue(APIByterFactory[TxEssenceOutput](p.API)(output), outputHasher)
if err != nil {
return EmptyOutputID, ierrors.Wrapf(err, "failed to check if proof contains output")
}

// The proof does not contain a hash of the output
if !contains {
return EmptyOutputID, ierrors.Errorf("proof does not contain the given output")
}

// Hash the proof to get the root
outputCommitment := Identifier(p.OutputCommitmentProof.Hash(outputHasher))

// Compute the output ID from the contents of the proof
utxoInput := &UTXOInput{
TransactionID: TransactionIDFromTransactionCommitmentAndOutputCommitment(p.Slot, p.TransactionCommitment, outputCommitment),
TransactionOutputIndex: p.OutputIndex,
}
computedOutputID := utxoInput.OutputID()

return computedOutputID, nil
}

type APIByter[T any] struct {
API API
Value T `serix:"0"`
}

func APIByterFactory[T any](api API) func(value T) *APIByter[T] {
return func(value T) *APIByter[T] {
return &APIByter[T]{
API: api,
Value: value,
}
}
}

func (a *APIByter[T]) SetDeserializationContext(ctx context.Context) {
a.API = APIFromContext(ctx)
}

func (a *APIByter[T]) Bytes() ([]byte, error) {
return a.API.Encode(a.Value)
}
138 changes: 138 additions & 0 deletions output_id_proof_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package iotago_test

import (
"fmt"
"testing"

"github.com/samber/lo"
"github.com/stretchr/testify/require"

iotago "github.com/iotaledger/iota.go/v4"
"github.com/iotaledger/iota.go/v4/tpkg"
)

type outputIDProofTest struct {
name string
tx *iotago.Transaction
}

func TestOutputIDProof(t *testing.T) {
ident1 := tpkg.RandEd25519Address()

inputIDs := tpkg.RandOutputIDs(1)

tests := []outputIDProofTest{
{
name: "single output",
tx: &iotago.Transaction{
API: tpkg.TestAPI,
TransactionEssence: &iotago.TransactionEssence{
CreationSlot: tpkg.RandSlot(),
NetworkID: tpkg.TestNetworkID,
Inputs: inputIDs.UTXOInputs(),
Capabilities: iotago.TransactionCapabilitiesBitMask{},
},
Outputs: lo.RepeatBy(1, func(_ int) iotago.TxEssenceOutput {
return &iotago.BasicOutput{
Amount: OneMi,
Conditions: iotago.BasicOutputUnlockConditions{
&iotago.AddressUnlockCondition{Address: ident1},
},
}
}),
},
},
{
name: "two outputs",
tx: &iotago.Transaction{
API: tpkg.TestAPI,
TransactionEssence: &iotago.TransactionEssence{
CreationSlot: tpkg.RandSlot(),
NetworkID: tpkg.TestNetworkID,
Inputs: inputIDs.UTXOInputs(),
Capabilities: iotago.TransactionCapabilitiesBitMask{},
},
Outputs: lo.RepeatBy(2, func(_ int) iotago.TxEssenceOutput {
return &iotago.BasicOutput{
Amount: OneMi,
Conditions: iotago.BasicOutputUnlockConditions{
&iotago.AddressUnlockCondition{Address: ident1},
},
}
}),
},
},
{
name: "three outputs",
tx: &iotago.Transaction{
API: tpkg.TestAPI,
TransactionEssence: &iotago.TransactionEssence{
CreationSlot: tpkg.RandSlot(),
NetworkID: tpkg.TestNetworkID,
Inputs: inputIDs.UTXOInputs(),
Capabilities: iotago.TransactionCapabilitiesBitMask{},
},
Outputs: lo.RepeatBy(3, func(_ int) iotago.TxEssenceOutput {
return &iotago.BasicOutput{
Amount: OneMi,
Conditions: iotago.BasicOutputUnlockConditions{
&iotago.AddressUnlockCondition{Address: ident1},
},
}
}),
},
},
{
name: "max outputs",
tx: &iotago.Transaction{
API: tpkg.TestAPI,
TransactionEssence: &iotago.TransactionEssence{
CreationSlot: tpkg.RandSlot(),
NetworkID: tpkg.TestNetworkID,
Inputs: inputIDs.UTXOInputs(),
Capabilities: iotago.TransactionCapabilitiesBitMask{},
},
Outputs: lo.RepeatBy(iotago.MaxOutputsCount, func(_ int) iotago.TxEssenceOutput {
return &iotago.BasicOutput{
Amount: OneMi,
Conditions: iotago.BasicOutputUnlockConditions{
&iotago.AddressUnlockCondition{Address: ident1},
},
}
}),
},
},
}

for _, tt := range tests {
t.Run(tt.name, tt.testOutputs)
}
}

func (p *outputIDProofTest) testOutputs(t *testing.T) {
outputSet, err := p.tx.OutputsSet()
require.NoError(t, err)

for outputID, output := range outputSet {
proof, err := iotago.OutputIDProofForOutputAtIndex(p.tx, outputID.Index())
require.NoError(t, err)

serializedProof, err := proof.Bytes()
require.NoError(t, err)

jsonEncoded, err := tpkg.TestAPI.JSONEncode(proof)
require.NoError(t, err)
fmt.Println(string(jsonEncoded))

deserializedProof, consumedBytes, err := iotago.OutputIDProofFromBytes(tpkg.TestAPI)(serializedProof)
require.NoError(t, err)
require.Equal(t, len(serializedProof), consumedBytes)

require.Equal(t, proof, deserializedProof)

computedOutputID, err := deserializedProof.OutputID(output)
require.NoError(t, err)

require.Equal(t, outputID, computedOutputID)
}
}
Loading

0 comments on commit 672944a

Please sign in to comment.