Bind service time and enforce request timeouts - #83
Open
ristik wants to merge 7 commits into
Open
Conversation
The leaf value the Unicity Service records becomes H(txhash, tau) instead of txhash alone, where tau is the reference time of the round the request was validated in. Certified transactions carry tau and verification uses the carried value. Predicate evaluation takes tau as an argument, but tau was recoverable only from the inclusion proof, as UC.IR.t. The tree is append-only, so a leaf can be certified afresh against any later root, and a later proof carries a later round's IR.t. Reference time was therefore a property of the proof rather than of the leaf, and re-presenting a leaf changed the predicate evaluation outcome. Binding it into the leaf value fixes the value the transition was validated under, for any proof of that leaf. A client learns tau from the inclusion proof, which now carries it: it cannot be recovered from the certificate chain, because an aggregator serves proofs against the current certified root rather than the one the leaf was created under. CertifiedMintTransaction and CertifiedTransferTransaction fix tau when they are first bound to a proof, and every later verification recomputes the leaf value from that carried value. Predicate verification takes tau as its second argument. No built-in predicate reads it yet; the point is that a registered engine can, and gets the same value on every re-validation. Wire changes, none backward compatible: InclusionProof [version, certData, tau, cert, uc] certified transaction [transaction, tau, inclusionProof] Refs #81
A transaction now carries an exclusive timeout tau_Q. The Unicity
Service accepts the request only in a round whose reference time
satisfies tau < tau_Q; an expired request is rejected. The timeout
constrains validation (inclusion to SMT) only: certification and
delivery may occur later.
Wire changes, not backward compatible:
MintTransaction [version, networkId, recipient, salt,
tokenType, justification, data, tau_Q]
TransferTransaction [version, recipient, stateMask, data, tau_Q]
CertificationData [version, lockScript, sourceStateHash,
transactionHash, tau_Q, witness]
Refs #82
Wire profiles, distinguished by the version field:
MintTransaction v1 [1, networkId, recipient, salt, tokenType,
justification, data]
v2 [2, ..., tau_Q]
TransferTransaction v1 [1, recipient, stateMask, data]
v2 [2, ..., tau_Q]
CertificationData v1 [1, lockScript, sourceStateHash, transactionHash,
witness]
v2 [2, ..., tau_Q, witness]
Verification enforces tau < tau_Q only where a timeout is explicit, and
requires the certification data to declare the same timeout the
transaction commits to. The reference time carried by a certified
transaction is unaffected by the profile and is required to agree with
the reference time in the attached proof.
CrossSdkEncodingTest pins both profiles against the vectors the
TypeScript and Rust SDKs and the aggregator assert on. Java had no
byte-level vector before.
Refs #82
MintTransaction, TransferTransaction and CertificationData each carried the optional request timeout as two wire versions: version 1 without the field, version 2 with it. The version was then derived from the field rather than read, so it carried no information, and CertificationData had given up its fixed-length decode check to accommodate the two shapes. Use one shape per structure. The deadline keeps a fixed position and is encoded as CBOR null when the caller did not supply one, which is how `data` and `justification` are already encoded in these same arrays. Version 2 is the only accepted version, the element count is fixed again, and both are checked once. The explicit-deadline bytes are unchanged; only the absent case moves, from a shorter array to a null in the slot. Absence is a null Long rather than a zero long, and the accessor is Optional<Long> getExpiresAt(), matching Optional<byte[]> getData() on the same interface. Zero is a legal instant, so the sentinel could not express "no deadline". Rename timeout to expiresAt: the value is an absolute exclusive instant in Unix seconds, not a duration. Replace the 18 MintTransaction.create overloads with a builder. The deadline was the fifth optional parameter and adding it had doubled an already combinatorial overload set. Required arguments go to MintTransaction.builder(networkId, recipient) and optional ones are named, so a further optional field is additive. Verification is unchanged in substance: an explicit deadline is enforced as an exclusive bound, and a request that carried none was admitted under a service-assigned deadline that is not recorded and is not re-checked. CrossSdkEncodingTest now pins both cases against the bytes the TypeScript SDK produces.
Same three defects the TypeScript review found, in the same places: - CertifiedMintTransaction.fromCbor and CertifiedTransferTransaction .fromCbor threw IllegalArgumentException for a decode-shape failure. They throw CborSerializationException now, like every other decoder. - The `getReferenceTime().isPresent() ||` guards sat in front of an inequality that an absent value already fails, so the guard only hid what the comparison was doing. Compare the Optionals directly. - The rule's MISSING_REFERENCE_TIME fires when the proof's reference time differs from the one the transition carries, which is what testVerificationFailsWithWrongReferenceTime was exercising. That case is REFERENCE_TIME_MISMATCH; MISSING_REFERENCE_TIME stays for the genuinely absent case in the certified transactions and InclusionProofUtils. InclusionProof's certification data, reference time and inclusion certificate describe a leaf and belong together: all three are present once the request is in a certified round, and all three are absent while it is pending. fromCbor now rejects any proof carrying some but not all of them, so the invariant holds once at the decode boundary instead of being re-checked at each use.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
expiresAt, in the transaction and certification wire formatsexpiresAtwritten as CBOR null when the caller did not supply oneMintTransaction.createoverloads with a builderWire format
One version and one element count per structure.
expiresAtoccupies a fixedposition and is
uint | null, which is howdataandjustificationarealready encoded in these same arrays.
The explicit-deadline bytes are unchanged from the earlier two-profile encoding.
Only the absent case moves, from a shorter array to a null in the same slot.
CertificationDataalso regains the fixed-length decode check it had given up toaccommodate two shapes.
An explicit deadline is committed by the transaction hash and enforced as an
exclusive bound. When it is absent the service assigns a deadline from consensus
reference time; that value stays service metadata, outside the leaf and outside
the signature, and no later verifier checks it. Omitting the deadline needs no
client clock.
API
Absence is a null
Long, not a zerolong: zero is a legal instant, so thesentinel could not express "no deadline". The accessor is
Optional<Long> getExpiresAt(), matchingOptional<byte[]> getData()on the same interface.The deadline was the fifth optional parameter on
MintTransaction.create, andadding it had doubled an already combinatorial overload set from 9 to 18. A
builder takes the required arguments and names the rest:
Validation
Cross-implementation:
CrossSdkEncodingTestpins the encoding for both a presentand an absent deadline against the bytes the TypeScript SDK produces. The Rust
SDK's
transition_flowvectors and rugregator's golden request vector assert onthe same bytes.
Refs #81
Refs #82