Skip to content

Commit 999ceca

Browse files
committed
Add missing relation for asset-id for public auditor keys, remove un-neccesary relation and update specs
1 parent e341dba commit 999ceca

44 files changed

Lines changed: 3369 additions & 992 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/dart-bp-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
uses: dtolnay/rust-toolchain@nightly
6262

6363
- name: Run negative tests by disabling prover's input sanitation and mocking features
64-
run: cargo +nightly nextest run --package polymesh-dart-bp --features ignore_prover_input_sanitation,nightly_mocking_tests input_sanitation_disabled
64+
run: cargo +nightly nextest run --package polymesh-dart-bp --features ignore_prover_input_sanitation,nightly_mocking_tests input_sanitation_disabled mocking_tests
6565

6666
test-wasm:
6767
name: Build dart-bp for WASM target

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ cargo nextest run -r --test serialized_objects_test
6262

6363
To create the serialized data, run
6464
```
65-
cargo nextest run -r --test serialized_objects_test --run-ignored ignored
65+
cargo nextest run -r --test serialized_objects_test --run-ignored only
6666
```
6767

6868
## Code coverage

dart-bp/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
4343
lazy_static = { workspace = true, default-features = false, optional = true }
4444
itertools = { version = "0.14.0", default-features = false }
4545
mocktopus = { version = "0.8.0", optional = true }
46+
creusot-std = "0.11"
4647

4748
[dev-dependencies]
4849
test-log = { workspace = true }

dart-bp/docs/1.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,28 @@ The chain allows 1 public key to have only 1 account per asset-id. This is done
316316
This is to ensure that a public key can do an accurate proof-of-balance for that asset without hiding any amount of that asset. In the future, we might support asset-id privacy during registration by the chain keeping a commitment of the assets per public key
317317
and during registration, a proof of uniqueness of the new asset-id against the committed set is provided (likely using a linear cost non-membership proof).
318318

319+
320+
### Workflows
321+
The system supports 3 workflows for producing the proofs described in the rest of this document. The protocols are largely the
322+
same across them; the only thing that changes is how secret keys are handled (and consequently how the proving/verification work is split between 2 parties).
323+
The idea is to use homomorphism of Pedersen commitments, which account commitments are, and split the commitment into two parts - one part containing the secret keys
324+
and other part without them. Each party does its proof over its corresponding part and the verifier "combines" these 2 parts appropriately. Sometimes the parts can
325+
just be added by the verifier to form the final commitment. However, sometimes revealing the parts can leak information so both parties blind their commitment with cancelling
326+
randomness, i.e. $r$ and $-r$ which hides each commitment but during "combining", it cancels out.
327+
328+
1. **W1 (Solo)**: A single prover holds all witnesses, including the secret keys $sk$ and $ek$, and produces the whole proof on its own. The protocols in the remaining sections are written assuming W1.
329+
330+
2. **W2 (Parallel)**: Two provers — a device (e.g. Ledger) which holds the secret keys, and a host (the user's computer) which does not. How the commitment gets split depends on whether $AK$ and $EK$ are revealed for the transaction:
331+
332+
- **When $AK$ and $EK$ are revealed** (account registration, minting): they become public to the verifier, so the device just proves knowledge of $sk, ek$ in $AK = sk.G_{Aff}$ and $EK = ek.G_{Enc}$, and the host's part can have these subtracted out.
333+
- **When $AK$ and $EK$ stay hidden inside the account commitment** (fee payment, transparent deposit/withdraw, affirmations): the device's part carries the secret-key terms together with a share of the commitment randomness, and the host's part carries
334+
everything else with the complementary share. Leg ciphertexts in affirmations are split the same way - the device contributes the $ek^{-1}$ and its proof and the host contributes the plaintext component ($amount$, $asset\_id$), its proofs, range proofs,
335+
curve tree membership proofs and other relations which use Bulletproofs. Thus all heavy lifting is done by the host.
336+
337+
The two proofs run independently and in parallel, bound together by feeding the same nonce/context and the same public inputs (account commitments, nullifier, re-randomized leaf, leg ciphertexts, etc.) into both transcripts. The exact split is described in each protocol's section.
338+
339+
3. **W3 (Sequential)**: Same split of work as W2 between device and host, but ordered. The host runs its pre-challenge phase first: it commits to its randomness, writes its instance to the transcript and derives its challenge $c_H$. It then sends $c_H$ to the device. The device sets its $nonce$ to $c_H \| nonce$ (concatenation of the byte serialization of $c_H$ and the original $nonce$), runs its proof against that combined nonce and sends the proof back. The host appends the device's proof bytes to its transcript, derives a fresh challenge $c_H'$ and generates its responses against $c_H'$. The verifier mirrors the same ordering.
340+
341+
There is no technical reason to use W3 over W2, but still ....
342+
319343
----------------------------------------

dart-bp/docs/2.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ A successful completion of registration results in the following:
5555

5656

5757
### Protocol
58+
59+
_The following description is for W1 (solo) where a single prover knows both secret keys $sk$ and $ek$ along with all other witnesses. The split-prover variant used in W2/W3 is described after the Verifier section below._
60+
5861
Here the prover (investor) wants to prove following relations:
5962

6063
1. $State_0 = sk.G_{Aff} + at.G_3 + \rho.G_4 + \rho^2.G_5 + s.G_6 + s^2.G_7 + id.G_8 + ek.G_{Enc}$. This is equivalent to proving $State_0 = AK + EK + at.G_3 + id.G_8 + \rho.G_4 + \rho^2.G_5 + s.G_6 + s^2.G_7$ since public key $AK$, encryption key $EK$, asset-id $at$ and identity $id$ are revealed to the verifier. So only $\rho, \rho^2, s, s^2$ need to be proved.
@@ -196,7 +199,54 @@ Above are the high level instance and witness values known before the protocol s
196199
14. Verifier finally checks the Bulletproofs proof that verifies all constraints, i.e for Poseidon2, multiplications and range proofs
197200

198201

199-
## Encrytion key distribution
202+
### Split prover variant (W2/W3)
203+
204+
In W2/W3 the work is split between a host (which does not know $sk, ek$) and a device (which holds $sk, ek$). The host runs essentially the same protocol as above except its statement excludes the secret-key components: since $AK$ and $EK$ are public, the verifier can subtract them out, so the host only proves knowledge of the remaining witnesses in
205+
$$
206+
State_{0_H} = at.G_3 + \rho.G_4 + \rho^2.G_5 + s.G_6 + s^2.G_7 + id.G_8
207+
$$
208+
i.e. $State_0$ minus $AK$ and $EK$. Everything else the host does (nullifier proof, encryption-of-$s$ and $\rho$ to $pk_T$, range proofs, Poseidon2 constraint, etc.) is unchanged. The verifier reconstructs $State_0 = State_{0_H} + AK + EK$ and uses it in the original checks.
209+
210+
The device produces a standalone Sigma proof of knowledge of $sk$ in $AK = sk.G_{Aff}$ and $ek$ in $EK = ek.G_{Enc}$. Both relations share the same challenge (single transcript).
211+
212+
**Instance**: $AK, EK, G_{Aff}, G_{Enc}$
213+
214+
**Witness**: $sk, ek$
215+
216+
#### Device prover
217+
1. Pick random $sk_r, ek_r \in \mathbb{Z}_p$ and compute
218+
$$T_{AK} = sk_r.G_{Aff}, \quad T_{EK} = ek_r.G_{Enc}$$
219+
2. Hash the device's transcript to get a challenge
220+
$$c = Hash(nonce, AK, EK, T_{AK}, T_{EK})$$
221+
3. Responses
222+
$$s_{sk} = sk_r + sk.c, \quad s_{ek} = ek_r + ek.c$$
223+
224+
The device sends $(T_{AK}, T_{EK}, s_{sk}, s_{ek})$ to the verifier (or, in W3, to the host first).
225+
226+
#### Device verifier
227+
1. Recompute the challenge:
228+
$$c = Hash(nonce, AK, EK, T_{AK}, T_{EK})$$
229+
2. Verify the affirmation key relation:
230+
$$s_{sk}.G_{Aff} \stackrel{?}{=} T_{AK} + AK.c$$
231+
$$\implies (sk_r + sk.c).G_{Aff} \stackrel{?}{=} sk_r.G_{Aff} + AK.c$$
232+
3. Verify the encryption key relation:
233+
$$s_{ek}.G_{Enc} \stackrel{?}{=} T_{EK} + EK.c$$
234+
$$\implies (ek_r + ek.c).G_{Enc} \stackrel{?}{=} ek_r.G_{Enc} + EK.c$$
235+
236+
#### Binding host and device
237+
238+
**W2 (parallel).** The device's $nonce$ is the same $nonce$ the host uses, so both proofs end up bound to the same context. The two proofs are produced independently and verified independently; nothing flows from one prover to the other during proving.
239+
240+
**W3 (sequential).** The host first runs its pre-challenge phase of the Prover above — picking all blindings and computing all $T$ values on the host side, but dropping the parts that involve $sk$ or $ek$ (so $T_{pk}$ and $T_{EK}$ are not computed and not added to its transcript). It then derives its challenge from that transcript exactly as the W1 challenge $c$ is derived, but with $T_{pk}$ and $T_{EK}$ omitted from the hash; call this challenge $c_H$. The host sends $c_H$ to the device. The device sets its $nonce$ to
241+
$$nonce' = c_H \| nonce$$
242+
(concatenation of the byte serialization of $c_H$ and the original $nonce$), runs the Device prover above against $nonce'$ — so its challenge becomes
243+
$$c = Hash(nonce', AK, EK, T_{AK}, T_{EK}) = Hash((c_H \| nonce), AK, EK, T_{AK}, T_{EK})$$
244+
— and sends $(T_{AK}, T_{EK}, s_{sk}, s_{ek})$ back to the host. The host appends the device proof bytes to its transcript and derives a fresh challenge
245+
$$c_H' = Hash(c_H, T_{AK}, T_{EK}, s_{sk}, s_{ek})$$
246+
which it uses for its own response generation. The verifier mirrors the same ordering: derives $c_H$, reconstructs $nonce' = c_H \| nonce$, verifies the device proof against $nonce'$, then derives $c_H'$ from the device proof bytes and verifies the host's responses against $c_H'$.
247+
248+
249+
## Encryption key distribution
200250
When an asset has a shared encryption key, the asset issuer distributes the corresponding secret key $ek$ to the relevant auditors/mediators using Twisted Elgamal and a key-distribution proof which proves to the chain that issuer has indeed encrypted the secret key for the intended recipients and they can decrypt to get it. The issuer splits $ek$ into chunks $ek_i$ and for each chunk produces:
201251
- A shared ciphertext: $C_i = G_{Enc}.r_i + H.ek_i$
202252
- A per-recipient ciphertext: $E_{ji} = EK_j.r_i$ for each recipient $j$ with encryption public key $EK_j$

0 commit comments

Comments
 (0)