|
17 | 17 | // * onchain contract based redemption with prevention of double redemptions |
18 | 18 |
|
19 | 19 | // How does that work: |
20 | | - // * a tracker holds A -> B debt (as positive number), along with ever increasing (on every operation) timestamp. |
| 20 | + // * a tracker holds ever created A -> B debt (as positive ever increasing number), along with ever increasing (on every operation) timestamp. |
21 | 21 | // A key->value dictionary is used to store the data as hash(AB) -> (amount, timestamp, sig_A), where AB is concatenation of public |
22 | 22 | // keys A and B, "amount" is amount of debt of A before B, timestamp is operation timestamp (in milliseconds), sig_A is signature of A for |
23 | 23 | // A for message (hash(AB), amount, timestamp). |
|
26 | 26 | // * tracker is periodically committing to its state (dictionary) by posting its digest on chain |
27 | 27 | // * at any moment it is possible to redeem A debt to B by calling redemption action of the reserve contract below |
28 | 28 | // B -> timestamp pair is written into the contract box. Calling the contract after with timestamp <= written on is |
29 | | - // prohibited. Tracker signature is needed to redeem. On next operation with tracker, debt of A is decreased. |
| 29 | + // prohibited. Tracker signature is needed to redeem. |
30 | 30 | // If not, A is refusing to sign updated records. Tracker cant steal A's funds as A's signature is checked. |
31 | 31 | // * if tracker is going offline, possible to redeem without its signature, when at least one week passed |
32 | 32 | // * always possible to top up the reserve, to redeem, reserve holder is making an offchain payment to self (A -> A) |
33 | 33 | // and then redeem |
34 | 34 |
|
35 | 35 |
|
36 | 36 | // Data: |
37 | | - // - token #0 - identifying singleton token |
| 37 | + // - token #0 - identifying singleton (NFT) token |
38 | 38 | // - R4 - signing key (as a group element) |
39 | 39 | // - R5 - tree of timestamps redeemed (to avoid double spending, it should have insert-only flag set) |
40 | 40 | // - R6 - NFT id of tracker server (bytes) // todo: support multiple payment servers by using a tree |
|
47 | 47 | // - R4 - tracker's signing key |
48 | 48 | // - R5 - commitment to credit data |
49 | 49 |
|
| 50 | + // action and reserve output index. By passing them instead of hard-coding, we allow for multiple notes to be |
| 51 | + // redeemed at once, which can be used for atomic mutual debt clearing etc |
50 | 52 | val v = getVar[Byte](0).get |
51 | 53 | val action = v / 10 |
52 | | - val index = v % 10 |
| 54 | + val index = v % 10 // reserve output position |
53 | 55 |
|
54 | 56 | val ownerKey = SELF.R4[GroupElement].get // reserve owner's key |
55 | 57 | val selfOut = OUTPUTS(index) |
56 | 58 |
|
57 | | - // common checks for all the paths (not incl. ERG value check) |
| 59 | + // common checks for all the paths (not incl. ERG value and R5 check) |
58 | 60 | val selfPreserved = |
59 | 61 | selfOut.propositionBytes == SELF.propositionBytes && |
60 | 62 | selfOut.tokens == SELF.tokens && |
|
63 | 65 |
|
64 | 66 | if (action == 0) { |
65 | 67 | // redemption path |
| 68 | + // context extension variables used: |
| 69 | + // #1 - receiver pubkey (as a group element) |
| 70 | + // #2 - reserve owner's signature for the debt record |
| 71 | + // #3 - current debt amount |
| 72 | + // #4 - timestamp |
| 73 | + // #5 - proof for insertion into reserve's AVL+ tree |
| 74 | + // #6 - tracker's signature |
66 | 75 |
|
67 | 76 | // Tracker box holds the debt information as key-value pairs: AB -> (amount, timestamp) |
68 | 77 | val tracker = CONTEXT.dataInputs(0) // Data input: tracker box containing debt records |
69 | 78 | val trackerNftId = tracker.tokens(0)._1 // NFT token ID identifying the tracker |
70 | 79 | val trackerPubKey = tracker.R4[GroupElement].get // Tracker's public key for signature verification |
71 | 80 | val trackerTree = tracker.R5[AvlTree].get // AVL tree storing debt commitments from tracker |
72 | 81 | val expectedTrackerId = SELF.R6[Coll[Byte]].get // Expected tracker ID stored in reserve contract |
73 | | - val trackerIdCorrect = trackerNftId == expectedTrackerId // Verify tracker identity matches |
| 82 | + |
| 83 | + // Verify that tracker identity matches |
| 84 | + val trackerIdCorrect = trackerNftId == expectedTrackerId |
74 | 85 |
|
75 | 86 | val g: GroupElement = groupGenerator // Base point for elliptic curve operations |
76 | 87 |
|
77 | 88 | // Receiver of the redemption (creditor) |
78 | 89 | val receiver = getVar[GroupElement](1).get |
79 | 90 | val receiverBytes = receiver.getEncoded // Receiver's public key bytes |
80 | 91 |
|
81 | | - val ownerKeyBytes = ownerKey.getEncoded // Reserve owner's public key bytes |
| 92 | + val ownerKeyBytes = ownerKey.getEncoded // Reserve owner's public key (from R4 register) bytes |
82 | 93 |
|
83 | 94 | // Create key for debt record: hash(ownerKey || receiverKey) |
84 | 95 | val key = blake2b256(ownerKeyBytes ++ receiverBytes) |
|
156 | 167 |
|
157 | 168 | // Combine all validation conditions |
158 | 169 | sigmaProp(selfPreserved && |
| 170 | + trackerIdCorrect && |
159 | 171 | properTimestampTree && |
160 | 172 | properReserveSignature && |
161 | 173 | properlyRedeemed && |
|
164 | 176 | // top up |
165 | 177 | sigmaProp( |
166 | 178 | selfPreserved && |
167 | | - (selfOut.value - SELF.value >= 1000000000) && // at least 1 ERG added |
168 | | - selfOut.R5[AvlTree].get == SELF.R5[AvlTree].get |
| 179 | + selfOut.R5[AvlTree].get == SELF.R5[AvlTree].get && // R5 register preservation is not checked in selfPreserved |
| 180 | + (selfOut.value - SELF.value >= 1000000000) // at least 1 ERG added |
169 | 181 | ) |
170 | 182 | } else { |
171 | 183 | sigmaProp(false) |
|
0 commit comments