-
Notifications
You must be signed in to change notification settings - Fork 1
Dev/jgough/10216 support for veracity #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
3996ca2
Input massif reader into verify inclusion instead of azblob reader
343b16e
fixup
7ed467e
fixup
0c47336
fixup
361aae2
fixup refactor into app entries
b0c1ead
fixup2
38bd9c0
fixup
91911bf
fixup
1b4a324
fixup integration tests
b76b56e
fixup
4fe80de
fixup review comments
1036377
fixup
75bccc7
fixup
a024008
fixup
8a6168a
fixup
d68b61d
fixup
a8f7bb4
fixup add more unit tests
8d703a6
fixup
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,369 @@ | ||
| package app | ||
|
|
||
| import ( | ||
| "crypto/sha256" | ||
| "encoding/binary" | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" | ||
| "github.com/datatrails/go-datatrails-common/logger" | ||
| "github.com/datatrails/go-datatrails-merklelog/massifs" | ||
| "github.com/datatrails/go-datatrails-merklelog/mmr" | ||
| "github.com/google/uuid" | ||
| ) | ||
|
|
||
| /** | ||
| * AppEntry is the app provided data for a corresponding log entry. | ||
| * An AppEntry will derive fields used for log entry inclusion verification. | ||
| * | ||
| * The format for an MMR entry is the following: | ||
| * | ||
| * H( Domain | MMR Salt | Serialized Bytes) | ||
| * | ||
| * Where: | ||
| * * Domain - the hashing schema for the MMR Entry | ||
| * * MMR Salt - datatrails provided fields included in the MMR Entry (can be found in the corresponding Trie Value on the log) | ||
| * * Serialized Bytes - app (customer) provided fields in the MMR Entry, serialized in a consistent way. | ||
| * | ||
| * | ||
| * The format for a Trie Entry is the following: | ||
| * | ||
| * ( Trie Key | Trie Value ) | ||
| * | ||
| * Where the Trie Key is: | ||
| * | ||
| * H( Domain | LogId | AppId ) | ||
| * | ||
| * And Trie Value is: | ||
| * | ||
| * ( Extra Bytes | IdTimestamp ) | ||
| */ | ||
|
|
||
| const ( | ||
| MMRSaltSize = 32 | ||
|
|
||
| ExtraBytesSize = 24 | ||
|
|
||
| IDTimestapSizeBytes = 8 | ||
| ) | ||
|
|
||
| // AppEntryGetter gets fields from the app entry or derives | ||
| // | ||
| // fields from the app entry. | ||
| type AppEntryGetter interface { | ||
| AppID() string | ||
| LogID() []byte | ||
| LogTenant() (string, error) | ||
| ExtraBytes() []byte | ||
| SerializedBytes() []byte | ||
| Domain() byte | ||
|
|
||
| MMRIndex() uint64 | ||
| IDTimestamp() string | ||
| MMRSalt() ([]byte, error) | ||
| MMREntry() ([]byte, error) | ||
| } | ||
|
|
||
| // AppEntryMassifGetter gets the massif for a specific app entry. | ||
| type AppEntryMassifGetter interface { | ||
| Massif(options ...MassifGetterOption) (*massifs.MassifContext, error) | ||
| } | ||
|
|
||
| // AppEntryVerifier can be used to verify the inclusion of an app entry | ||
| // | ||
| // against its corresponding log entry. | ||
| type AppEntryVerifier interface { | ||
| Proof(options ...MassifGetterOption) ([][]byte, error) | ||
| VerifyProof(proof [][]byte, options ...MassifGetterOption) (bool, error) | ||
| VerifyInclusion(options ...MassifGetterOption) (bool, error) | ||
| } | ||
|
|
||
| // VerifiableAppEntry includes all methods that could be needed for a verifiable app entry. | ||
| type VerifiableAppEntry interface { | ||
| AppEntryGetter | ||
| AppEntryMassifGetter | ||
| AppEntryVerifier | ||
| } | ||
|
|
||
| // MMREntryFields are the fields that when hashed result in the MMR Entry | ||
| type MMREntryFields struct { | ||
|
|
||
| // domain defines the hashing schema for the MMR Entry | ||
| domain byte | ||
|
|
||
| // serializedBytes are app (customer) provided fields in the MMR Entry, serialized in a consistent way. | ||
| serializedBytes []byte | ||
| } | ||
|
|
||
| // AppEntry is the app provided data for a corresponding log entry. | ||
| // | ||
| // It contains key information for verifying inclusion of the corresponding log entry. | ||
| // | ||
| // NOTE: all fields are sourced from the app data, or derived from it. | ||
| // NONE of the fields in an AppEntry are sourced from the log. | ||
| type AppEntry struct { | ||
| // appID is an identifier of the app committing the merkle log entry | ||
| appID string | ||
|
|
||
| // logID is a uuid in byte form of the specific log identifier | ||
| logID []byte | ||
|
|
||
| // extraBytes are extrabytes provided by datatrails for the specific app | ||
| extraBytes []byte | ||
|
|
||
| // MMREntryFields used to determine the MMR Entry | ||
| mmrEntryFields *MMREntryFields | ||
|
|
||
| // MerkleLogCommit used to define information about the log entry | ||
| merkleLogCommit *assets.MerkleLogCommit | ||
| } | ||
|
|
||
| // NewAppEntry creates a new app entry entry | ||
| func NewAppEntry( | ||
| appId string, | ||
| logId []byte, | ||
| extraBytes []byte, | ||
| mmrEntryFields *MMREntryFields, | ||
| merklelogCommit *assets.MerkleLogCommit, | ||
| ) *AppEntry { | ||
|
|
||
| appEntry := &AppEntry{ | ||
| appID: appId, | ||
| logID: logId, | ||
| extraBytes: extraBytes, | ||
| mmrEntryFields: mmrEntryFields, | ||
| merkleLogCommit: merklelogCommit, | ||
| } | ||
|
|
||
| return appEntry | ||
| } | ||
|
|
||
| // MMREntry derives the mmr entry of the corresponding log entry from the app data. | ||
| // | ||
| // MMREntry is: | ||
| // - H( Domain | MMR Salt | Serialized Bytes) | ||
| func (ae *AppEntry) MMREntry() ([]byte, error) { | ||
honourfish marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| hasher := sha256.New() | ||
|
|
||
| // domain | ||
| hasher.Write([]byte{ae.mmrEntryFields.domain}) | ||
|
|
||
| // mmr salt | ||
| mmrSalt, err := ae.MMRSalt() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| hasher.Write(mmrSalt) | ||
|
|
||
| // serialized bytes | ||
| hasher.Write(ae.mmrEntryFields.serializedBytes) | ||
|
|
||
| return hasher.Sum(nil), nil | ||
|
|
||
| } | ||
|
|
||
| // MMRIndex gets the mmr index of the corresponding log entry. | ||
| func (ae *AppEntry) MMRIndex() uint64 { | ||
|
|
||
| if ae.merkleLogCommit == nil { | ||
| return 0 | ||
| } | ||
|
|
||
| return ae.merkleLogCommit.Index | ||
| } | ||
|
|
||
| // IDTimestamp gets the idtimestamp of the corresponding log entry. | ||
| func (ae *AppEntry) IDTimestamp() string { | ||
|
|
||
| if ae.merkleLogCommit == nil { | ||
| return "" | ||
| } | ||
|
|
||
| return ae.merkleLogCommit.Idtimestamp | ||
| } | ||
|
|
||
| // AppID gets the app id of the corresponding log entry. | ||
| func (ae *AppEntry) AppID() string { | ||
| return ae.appID | ||
| } | ||
|
|
||
| // LogID gets the log id of the corresponding log entry. | ||
| func (ae *AppEntry) LogID() []byte { | ||
| return ae.logID | ||
| } | ||
|
|
||
| // LogTenant returns the Log tenant that committed this app entry to the log | ||
| // as a tenant identity. | ||
| func (ae *AppEntry) LogTenant() (string, error) { | ||
|
|
||
| logTenantUuid, err := uuid.FromBytes(ae.logID) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| return fmt.Sprintf("tenant/%s", logTenantUuid.String()), nil | ||
|
|
||
| } | ||
|
|
||
| // ExtraBytes gets the extrabytes of the corresponding log entry. | ||
| func (ae *AppEntry) ExtraBytes() []byte { | ||
| return ae.extraBytes | ||
| } | ||
|
|
||
| // SerializedBytes gets the serialized bytes used to derive the mmr entry. | ||
| func (ae *AppEntry) SerializedBytes() []byte { | ||
| return ae.mmrEntryFields.serializedBytes | ||
| } | ||
|
|
||
| // Domain gets the domain byte used to derive the mmr entry. | ||
| func (ae *AppEntry) Domain() byte { | ||
| return ae.mmrEntryFields.domain | ||
| } | ||
|
|
||
| // MMRSalt derives the MMR Salt of the corresponding log entry from the app data. | ||
| // MMRSalt is the datatrails provided fields included on the MMR Entry. | ||
| // | ||
| // this is (extrabytes | idtimestamp) for any apps that adhere to log entry version 1. | ||
| func (ae *AppEntry) MMRSalt() ([]byte, error) { | ||
|
|
||
| mmrSalt := make([]byte, MMRSaltSize) | ||
|
|
||
| copy(mmrSalt[:ExtraBytesSize], ae.extraBytes) | ||
|
|
||
| // get the byte representation of idtimestamp | ||
| idTimestamp, _, err := massifs.SplitIDTimestampHex(ae.merkleLogCommit.Idtimestamp) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| idTimestampBytes := make([]byte, IDTimestapSizeBytes) | ||
| binary.BigEndian.PutUint64(idTimestampBytes, idTimestamp) | ||
|
|
||
| copy(mmrSalt[ExtraBytesSize:], idTimestampBytes) | ||
|
|
||
| return mmrSalt, nil | ||
| } | ||
|
|
||
| /** Massif gets the massif context, for the massif of the corresponding log entry from the app data. | ||
| * | ||
| * The following massif options can be used, in priority order: | ||
| * - WithMassifContext | ||
| * - WithMassifReader | ||
| * - WithAzblobReader | ||
| * | ||
| * Example WithMassifReader: | ||
| * | ||
| * WithMassifReader( | ||
| * reader, | ||
| * WithMassifTenantId("tenant/foo"), | ||
| * WithMassifHeight(14), | ||
| * ) | ||
| */ | ||
| func (ae *AppEntry) Massif(options ...MassifGetterOption) (*massifs.MassifContext, error) { | ||
|
|
||
| massifOptions := ParseMassifGetterOptions(options...) | ||
|
|
||
| // first check if the options give a massif context to use, and use that | ||
| if massifOptions.massifContext != nil { | ||
| return massifOptions.massifContext, nil | ||
| } | ||
|
|
||
| var massifReader MassifGetter | ||
| // now check if we have a massif reader | ||
| if massifOptions.massifGetter != nil { | ||
| massifReader = massifOptions.massifGetter | ||
| } else { | ||
| // otherwise use azblob reader to get it | ||
| if massifOptions.azblobReader == nil { | ||
| return nil, errors.New("no way of determining massif of app entry, please provide either a massif context, massif reader or azblob reader") | ||
| } | ||
|
|
||
| newMassifReader := massifs.NewMassifReader(logger.Sugar, massifOptions.azblobReader) | ||
| massifReader = &newMassifReader | ||
| } | ||
|
|
||
| massifHeight := massifOptions.MassifHeight | ||
|
|
||
| logIdentity := massifOptions.TenantId | ||
| // if the log identity is not given, attempt to find it from the logId | ||
| if massifOptions.TenantId == "" { | ||
| // find the tenant log from the logID | ||
| logUuid, err := uuid.FromBytes(ae.logID) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // log identity is currently `tenant/logid` | ||
| logIdentity = fmt.Sprintf("tenant/%s", logUuid.String()) | ||
| } | ||
|
|
||
| return Massif(ae.merkleLogCommit.Index, massifReader, logIdentity, massifHeight) | ||
|
|
||
| } | ||
|
|
||
| // Proof gets the inclusion proof of the corresponding log entry for the app data. | ||
| func (ae *AppEntry) Proof(options ...MassifGetterOption) ([][]byte, error) { | ||
|
|
||
| massif, err := ae.Massif(options...) | ||
|
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // Get the size of the complete tenant MMR | ||
| mmrSize := massif.RangeCount() | ||
|
|
||
| proof, err := mmr.InclusionProof(massif, mmrSize-1, ae.MMRIndex()) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return proof, nil | ||
| } | ||
|
|
||
| // VerifyProof verifies the given inclusion proof of the corresponding log entry for the app data. | ||
| func (ae *AppEntry) VerifyProof(proof [][]byte, options ...MassifGetterOption) (bool, error) { | ||
|
|
||
| massif, err := ae.Massif(options...) | ||
|
|
||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| // Get the size of the complete tenant MMR | ||
| mmrSize := massif.RangeCount() | ||
|
|
||
| hasher := sha256.New() | ||
|
|
||
| mmrEntry, err := ae.MMREntry() | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| return mmr.VerifyInclusion(massif, hasher, mmrSize, mmrEntry, | ||
| ae.MMRIndex(), proof) | ||
|
|
||
| } | ||
|
|
||
| // VerifyInclusion verifies the inclusion of the app entry | ||
| // against the corresponding log entry in immutable merkle log | ||
| // | ||
| // Returns true if the app entry is included on the log, otherwise false. | ||
| func (ae *AppEntry) VerifyInclusion(options ...MassifGetterOption) (bool, error) { | ||
|
|
||
| massif, err := ae.Massif(options...) | ||
|
|
||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| proof, err := ae.Proof(WithMassifContext(massif)) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| return ae.VerifyProof(proof, WithMassifContext(massif)) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.