From 5ea2b5588135b7d160235381ab9e6ab579a270f8 Mon Sep 17 00:00:00 2001 From: Benjamin Diamond <30356252+benediamond@users.noreply.github.com> Date: Sat, 18 Aug 2018 15:57:58 -0400 Subject: [PATCH 1/8] explain patch / instructions in readme --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 93a229f5d0..22f47d2ee0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ +# NOTE + +This branch patches a vulnerability described in the [Quorum ZSL Wiki](https://github.com/jpmorganchase/quorum/wiki/ZSL) and exploited in the [zsl_adversary](https://github.com/benediamond/quorum/tree/zsl_adversary) branch of this repository. In this branch, no `unshield` transaction can be mis-appropriated, even if an adversary intercepts, re-signs, and re-broadcasts its payload. + +The solution involves introducing a cryptographic link between each `unshield` proof and its prover's Ethereum account. Previously, an unshielder broadcasted a proof demonstrating that, given a public _committment_ `cm` (established by a prior `shield` transaction), a _spend nullifier_ `spend_nf`, and a _value_ `val`, the prover knows a _secret random nonce_ `rho` and a _spending key_ `sk` for which `spend_nf = SHA-256(0x01 | rho | sk)` and `cm = SHA-256(rho | pk | val)` (where `pk = SHA-256(sk)`). + +This proof, of course, asserts nothing about the _Ethereum_ identity of the prover, and should in fact be considered a secret: a malicious Raft leader, upon rebroadcasting the proof as his own, may steal its corresponding funds. + +We redefine a shielded note's _spend nullifier_ as `spend_nf = SHA-256(0x01 | rho | sk | addr)`, where `addr` is the _public Ethereum address of the prover_. A prover, then, demonstrates in zero knowledge that he knows `rho`, `sk` for which the public `spend_nf = SHA-256(0x01 | rho | sk | addr)`. The on-chain verifier verifies this proof by supplying `msg.sender` as `addr`. Under expected usage, the proof will authenticate; if `msg.sender` differs from the address supplied by the prover during proof generation, the verification will fail. + +No adversary, moreover, can forge a proof using the prover's broadcasted transaction. Its payload reveals, by construction, nothing about the hidden witnesses `rho` and `sk`; the security of this scheme, therefore, reduces to the difficulty of constructing from scratch the ("curried") SHA-256 preimages `rho` and `sk`. (The adversary could of course generate new values `rho` and `sk` and broadcast an altered spend nullifier; however, in this case note commitment integrity would fail.) + +This repository contains complete code, including precompiled contracts. The required minor modifications to the ZSL contracts can be found at [benediamond/zsl-q](https://github.com/benediamond/zsl-q). + +# Demonstration + +The instructions at `zsl_adversary`'s [README](https://github.com/benediamond/quorum/blob/zsl_adversary/README.md) should be followed exactly as before, except for the unshielding proof generation step: + +```javascript +result = zsl.createUnshielding(rho, sk, value, treeIndex, authPath) +``` +should be replaced with +```javascript +result = zsl.createUnshielding(rho, sk, eth.accounts[0], value, treeIndex, authPath) +``` +The call to `ztoken.unshield`, on the other hand, proceeds as before. Notice that a replay of the `unshield`ing transaction will fail to credit the adversary's balance; a properly authorized `unshield` will succeed. + +A general unshielding test suite can be accessed through `zsl.debugUnshielding()`. + # Quorum Quorum is an Ethereum-based distributed ledger protocol with transaction/contract privacy and a new consensus mechanism. From 43110eb4b92a06b306b132a1883aab4b2b307755 Mon Sep 17 00:00:00 2001 From: Benjamin Diamond <30356252+benediamond@users.noreply.github.com> Date: Sat, 18 Aug 2018 15:58:33 -0400 Subject: [PATCH 2/8] add contracts.go --- core/vm/contracts.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 66b0d11523..476cc4dae9 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -521,10 +521,12 @@ func (c *verifyUnshielding) Run(in []byte) ([]byte, error) { var spend_nf [32]byte var rt [32]byte + var addr [20]byte copy(spend_nf[:], in[:32]) copy(rt[:], in[32:64]) - noteValue := binary.BigEndian.Uint64(in[88:96]) - proofSize := binary.BigEndian.Uint64(in[120:128]) // should be 584 + copy(addr[:], in[76:96]) // type address === uint160 + noteValue := binary.BigEndian.Uint64(in[120:128]) + proofSize := binary.BigEndian.Uint64(in[152:160]) // should be 584 if proofSize != ZSL_PROOF_SIZE { msg := fmt.Sprintf("ZSL error, proof must have size of %d bytes, not %d.\n", ZSL_PROOF_SIZE, proofSize) @@ -533,9 +535,9 @@ func (c *verifyUnshielding) Run(in []byte) ([]byte, error) { } var proof [ZSL_PROOF_SIZE]byte - copy(proof[:], in[128:]) + copy(proof[:], in[160:]) - result := snark.VerifyUnshielding(proof, spend_nf, rt, noteValue) + result := snark.VerifyUnshielding(proof, spend_nf, rt, addr, noteValue) var b byte if result { b = 1 From edffaec87534a200b1485b0511f3a27a434d6b37 Mon Sep 17 00:00:00 2001 From: Benjamin Diamond <30356252+benediamond@users.noreply.github.com> Date: Sat, 18 Aug 2018 15:59:09 -0400 Subject: [PATCH 3/8] add api.go --- core/zsl/api.go | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/core/zsl/api.go b/core/zsl/api.go index 6108d554e4..99825dfc43 100644 --- a/core/zsl/api.go +++ b/core/zsl/api.go @@ -75,6 +75,16 @@ func computeSpendNullifier(rho []byte, sk [32]byte) []byte { return h.Sum(nil) } +// spend nullifier SHA256(0x01 || rho || sk || addr) +func computeSpendNullifierAuthenticated(rho []byte, sk [32]byte, addr [20]byte) []byte { + h := sha256.New() + h.Write([]byte{0x01}) + h.Write(rho) + h.Write(sk[:]) + h.Write(addr[:]) + return h.Sum(nil) +} + // cm = SHA256(rho || pk || v) where v is in little endian byte order func computeCommitment(rho [32]byte, pk [32]byte, v uint64) []byte { vbuf := make([]byte, 8) @@ -135,7 +145,7 @@ zsl.createUnshielding(rho, sk, value, treeIndex, authPath) Verify with: zsl.verifyUnshielding(proof, spend_nf, rt, value) */ -func (api *PublicZSLAPI) CreateUnshielding(rho common.Hash, sk common.Hash, value float64, treeIndex float64, authPath []string) (map[string]interface{}, error) { +func (api *PublicZSLAPI) CreateUnshielding(rho common.Hash, sk common.Hash, addr common.Address, value float64, treeIndex float64, authPath []string) (map[string]interface{}, error) { result := make(map[string]interface{}) // copy authentication path array into two dimensional array (as required by snark.ProveUnshielding()) @@ -154,9 +164,9 @@ func (api *PublicZSLAPI) CreateUnshielding(rho common.Hash, sk common.Hash, valu } snark.Init() - proof := snark.ProveUnshielding(rho, sk, uint64(value), uint64(treeIndex), authenticationPath) + proof := snark.ProveUnshielding(rho, sk, addr, uint64(value), uint64(treeIndex), authenticationPath) send_nf := computeSendNullifier(rho[:]) - spend_nf := computeSpendNullifier(rho[:], sk) + spend_nf := computeSpendNullifierAuthenticated(rho[:], sk, addr) result["proof"] = "0x" + hex.EncodeToString(proof[:]) result["send_nf"] = common.BytesToHash(send_nf) result["spend_nf"] = common.BytesToHash(spend_nf) @@ -264,9 +274,9 @@ func (api *PublicZSLAPI) VerifyShielding(proofHex string, send_nf common.Hash, c return result, nil } -// geth: zsl.verifyShielding(proof, spend_nf, rt, value); +// geth: zsl.verifyUnshielding(proof, spend_nf, rt, addr, value); // Javascript numbers are floats, there is no support for 64-bit integers. -func (api *PublicZSLAPI) VerifyUnshielding(proofHex string, spend_nf common.Hash, rt common.Hash, value float64) (bool, error) { +func (api *PublicZSLAPI) VerifyUnshielding(proofHex string, spend_nf common.Hash, rt common.Hash, addr common.Address, value float64) (bool, error) { proof, err := getProofFromHex(proofHex) if err != nil { @@ -274,7 +284,7 @@ func (api *PublicZSLAPI) VerifyUnshielding(proofHex string, spend_nf common.Hash } snark.Init() - result := snark.VerifyUnshielding(proof, spend_nf, rt, uint64(value)) + result := snark.VerifyUnshielding(proof, spend_nf, rt, addr, uint64(value)) return result, nil } @@ -337,7 +347,7 @@ func (api *PublicZSLAPI) DebugShielding() (bool, error) { [32]byte{0xd6, 0x21, 0x8a, 0x07, 0x71, 0x4d, 0xd9, 0xfa, 0xc9, 0x2b, 0xde, 0xef, 0xd3, 0xf4, 0xc0, 0xd7, 0x69, 0xf4, 0x0f, 0x4b, 0x04, 0x3a, 0xd7, 0xe6, 0x7e, 0x73, 0x75, 0xed, 0xc4, 0x98, 0xe4, 0x5c}, 2378237) if !result { - return false, errors.New("proof was not valid as expected") + return false, errors.New("proof was not invalid as expected") } result = snark.VerifyShielding(proof, @@ -367,6 +377,7 @@ func (api *PublicZSLAPI) DebugUnshielding() (bool, error) { proof := snark.ProveUnshielding( [32]byte{0xde, 0xde, 0xff, 0xdd, 0xde, 0xde, 0xff, 0xdd, 0xde, 0xde, 0xff, 0xdd, 0xde, 0xde, 0xff, 0xdd, 0xde, 0xde, 0xff, 0xdd, 0xde, 0xde, 0xff, 0xdd, 0xde, 0xde, 0xff, 0xdd, 0xde, 0xde, 0xff, 0xdd}, [32]byte{0xf0, 0xf0, 0xf0, 0xf0, 0x0f, 0x0f, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0xf0, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff}, + [20]byte{0xed, 0x9d, 0x02, 0xe3, 0x82, 0xb3, 0x48, 0x18, 0xe8, 0x8b, 0x88, 0xa3, 0x09, 0xc7, 0xfe, 0x71, 0xe6, 0x5f, 0x41, 0x9d}, 2378237, 0, [29][32]byte{ @@ -401,32 +412,45 @@ func (api *PublicZSLAPI) DebugUnshielding() (bool, error) { {0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0x00}}) result := snark.VerifyUnshielding(proof, - [32]byte{0x45, 0xcc, 0xb2, 0x10, 0x61, 0x33, 0x18, 0xd0, 0xd1, 0x27, 0xe9, 0x94, 0x7c, 0x2c, 0xe6, 0xb1, 0xb2, 0x46, 0xc5, 0xc2, 0xdd, 0x53, 0x48, 0x9c, 0x1f, 0x39, 0x39, 0x78, 0x43, 0x41, 0x0c, 0x1a}, + [32]byte{0x3a, 0x2c, 0xeb, 0xa3, 0x2e, 0xf0, 0x24, 0x56, 0xd5, 0x19, 0xf0, 0x66, 0xe6, 0xb6, 0xf8, 0xa2, 0x72, 0xb6, 0x88, 0x4a, 0xb5, 0x73, 0x61, 0x58, 0xc6, 0x30, 0x8e, 0x04, 0x38, 0x2a, 0x78, 0xaf}, [32]byte{0x86, 0x10, 0x65, 0x27, 0x39, 0xac, 0x0c, 0x6b, 0xb6, 0xb5, 0x35, 0x36, 0x49, 0xbb, 0x82, 0x2b, 0x26, 0x54, 0x3f, 0x0e, 0xbe, 0x88, 0xf3, 0x2a, 0x48, 0x9a, 0x56, 0x84, 0x3c, 0xd0, 0x4f, 0x03}, + [20]byte{0xed, 0x9d, 0x02, 0xe3, 0x82, 0xb3, 0x48, 0x18, 0xe8, 0x8b, 0x88, 0xa3, 0x09, 0xc7, 0xfe, 0x71, 0xe6, 0x5f, 0x41, 0x9d}, 2378237) if !result { return false, errors.New("proof was not valid as expected") } result = snark.VerifyUnshielding(proof, - [32]byte{0x45, 0xcc, 0xb2, 0x10, 0x61, 0x33, 0x18, 0xd0, 0xd1, 0x27, 0xe9, 0x94, 0x7c, 0x2c, 0xe6, 0xb1, 0xb2, 0x46, 0xc5, 0xc2, 0xdd, 0x53, 0x48, 0x9c, 0x1f, 0x39, 0x39, 0x78, 0x43, 0x41, 0x0c, 0x1a}, + [32]byte{0x3a, 0x2c, 0xeb, 0xa3, 0x2e, 0xf0, 0x24, 0x56, 0xd5, 0x19, 0xf0, 0x66, 0xe6, 0xb6, 0xf8, 0xa2, 0x72, 0xb6, 0x88, 0x4a, 0xb5, 0x73, 0x61, 0x58, 0xc6, 0x30, 0x8e, 0x04, 0x38, 0x2a, 0x78, 0xaf}, [32]byte{0x86, 0x10, 0x65, 0x27, 0x39, 0xac, 0x0c, 0x6b, 0xb6, 0xb5, 0x35, 0x36, 0x49, 0xbb, 0x82, 0x2b, 0x26, 0x54, 0x3f, 0x0e, 0xbe, 0x88, 0xf3, 0x2a, 0x48, 0x9a, 0x56, 0x84, 0x3c, 0xd0, 0x4f, 0x03}, + [20]byte{0xed, 0x9d, 0x02, 0xe3, 0x82, 0xb3, 0x48, 0x18, 0xe8, 0x8b, 0x88, 0xa3, 0x09, 0xc7, 0xfe, 0x71, 0xe6, 0x5f, 0x41, 0x9d}, 2378236) if result { return false, errors.New("proof was not invalid as expected") } result = snark.VerifyUnshielding(proof, - [32]byte{0x44, 0xcc, 0xb2, 0x10, 0x61, 0x33, 0x18, 0xd0, 0xd1, 0x27, 0xe9, 0x94, 0x7c, 0x2c, 0xe6, 0xb1, 0xb2, 0x46, 0xc5, 0xc2, 0xdd, 0x53, 0x48, 0x9c, 0x1f, 0x39, 0x39, 0x78, 0x43, 0x41, 0x0c, 0x1a}, + [32]byte{0x39, 0x2c, 0xeb, 0xa3, 0x2e, 0xf0, 0x24, 0x56, 0xd5, 0x19, 0xf0, 0x66, 0xe6, 0xb6, 0xf8, 0xa2, 0x72, 0xb6, 0x88, 0x4a, 0xb5, 0x73, 0x61, 0x58, 0xc6, 0x30, 0x8e, 0x04, 0x38, 0x2a, 0x78, 0xaf}, [32]byte{0x86, 0x10, 0x65, 0x27, 0x39, 0xac, 0x0c, 0x6b, 0xb6, 0xb5, 0x35, 0x36, 0x49, 0xbb, 0x82, 0x2b, 0x26, 0x54, 0x3f, 0x0e, 0xbe, 0x88, 0xf3, 0x2a, 0x48, 0x9a, 0x56, 0x84, 0x3c, 0xd0, 0x4f, 0x03}, + [20]byte{0xed, 0x9d, 0x02, 0xe3, 0x82, 0xb3, 0x48, 0x18, 0xe8, 0x8b, 0x88, 0xa3, 0x09, 0xc7, 0xfe, 0x71, 0xe6, 0x5f, 0x41, 0x9d}, 2378237) if result { return false, errors.New("proof was not invalid as expected") } result = snark.VerifyUnshielding(proof, - [32]byte{0x45, 0xcc, 0xb2, 0x10, 0x61, 0x33, 0x18, 0xd0, 0xd1, 0x27, 0xe9, 0x94, 0x7c, 0x2c, 0xe6, 0xb1, 0xb2, 0x46, 0xc5, 0xc2, 0xdd, 0x53, 0x48, 0x9c, 0x1f, 0x39, 0x39, 0x78, 0x43, 0x41, 0x0c, 0x1a}, + [32]byte{0x39, 0x2c, 0xeb, 0xa3, 0x2e, 0xf0, 0x24, 0x56, 0xd5, 0x19, 0xf0, 0x66, 0xe6, 0xb6, 0xf8, 0xa2, 0x72, 0xb6, 0x88, 0x4a, 0xb5, 0x73, 0x61, 0x58, 0xc6, 0x30, 0x8e, 0x04, 0x38, 0x2a, 0x78, 0xaf}, [32]byte{0x88, 0x10, 0x65, 0x27, 0x39, 0xac, 0x0c, 0x6b, 0xb6, 0xb5, 0x35, 0x36, 0x49, 0xbb, 0x82, 0x2b, 0x26, 0x54, 0x3f, 0x0e, 0xbe, 0x88, 0xf3, 0x2a, 0x48, 0x9a, 0x56, 0x84, 0x3c, 0xd0, 0x4f, 0x03}, + [20]byte{0xed, 0x9d, 0x02, 0xe3, 0x82, 0xb3, 0x48, 0x18, 0xe8, 0x8b, 0x88, 0xa3, 0x09, 0xc7, 0xfe, 0x71, 0xe6, 0x5f, 0x41, 0x9d}, + 2378237) + if result { + return false, errors.New("proof was not invalid as expected") + } + + result = snark.VerifyUnshielding(proof, + [32]byte{0x3a, 0x2c, 0xeb, 0xa3, 0x2e, 0xf0, 0x24, 0x56, 0xd5, 0x19, 0xf0, 0x66, 0xe6, 0xb6, 0xf8, 0xa2, 0x72, 0xb6, 0x88, 0x4a, 0xb5, 0x73, 0x61, 0x58, 0xc6, 0x30, 0x8e, 0x04, 0x38, 0x2a, 0x78, 0xaf}, + [32]byte{0x86, 0x10, 0x65, 0x27, 0x39, 0xac, 0x0c, 0x6b, 0xb6, 0xb5, 0x35, 0x36, 0x49, 0xbb, 0x82, 0x2b, 0x26, 0x54, 0x3f, 0x0e, 0xbe, 0x88, 0xf3, 0x2a, 0x48, 0x9a, 0x56, 0x84, 0x3c, 0xd0, 0x4f, 0x03}, + [20]byte{0xec, 0x9d, 0x02, 0xe3, 0x82, 0xb3, 0x48, 0x18, 0xe8, 0x8b, 0x88, 0xa3, 0x09, 0xc7, 0xfe, 0x71, 0xe6, 0x5f, 0x41, 0x9d}, 2378237) if result { return false, errors.New("proof was not invalid as expected") From 7e71fa86806258da558b2ecee42ff720634e906f Mon Sep 17 00:00:00 2001 From: Benjamin Diamond <30356252+benediamond@users.noreply.github.com> Date: Sat, 18 Aug 2018 16:00:58 -0400 Subject: [PATCH 4/8] adjust function parameter counts --- internal/web3ext/web3ext.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 3cb795df52..0eac967657 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -767,7 +767,7 @@ web3._extend({ new web3._extend.Method({ name: 'createUnshielding', call: 'zsl_createUnshielding', - params: 5 + params: 6 }), new web3._extend.Method({ name: 'createShieldedTransfer', @@ -787,7 +787,7 @@ web3._extend({ new web3._extend.Method({ name: 'verifyUnshielding', call: 'zsl_verifyUnshielding', - params: 4 + params: 5 }), new web3._extend.Method({ name: 'getNewAddress', From 9d076082a8517ef763e238ce616ac2aae848a29a Mon Sep 17 00:00:00 2001 From: Benjamin Diamond <30356252+benediamond@users.noreply.github.com> Date: Sat, 18 Aug 2018 16:02:39 -0400 Subject: [PATCH 5/8] add modified zk-snark circuits --- .../zsl-q/zsl-golang/zsl/snark/gadgets.tcc | 146 +++++++++++++++++- .../zsl-q/zsl-golang/zsl/snark/impl.tcc | 12 +- 2 files changed, 150 insertions(+), 8 deletions(-) diff --git a/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/gadgets.tcc b/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/gadgets.tcc index 6b12b748a7..a4437cb530 100644 --- a/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/gadgets.tcc +++ b/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/gadgets.tcc @@ -74,7 +74,7 @@ uint64_t convertVectorToInt(const std::vector& v) { std::vector uint64_to_bool_vector(uint64_t input) { auto num_bv = convertIntToVectorLE(input); - + return convertBytesVectorToVector(num_bv); } @@ -374,6 +374,126 @@ public: } }; +template +class SpendNullifierAuthenticated : gadget { +private: + std::shared_ptr> block1; + std::shared_ptr> hasher1; + std::shared_ptr> intermediate; + std::shared_ptr> block2; + std::shared_ptr> hasher2; + +public: + SpendNullifierAuthenticated( + protoboard &pb, + pb_variable& ZERO, + pb_variable_array rho, + pb_variable_array sk, + pb_variable_array addr, + std::shared_ptr> result + ) : gadget(pb) { + pb_linear_combination_array IV = SHA256_default_IV(pb); + + pb_variable_array discriminants; + discriminants.emplace_back(ZERO); + discriminants.emplace_back(ZERO); + discriminants.emplace_back(ZERO); + discriminants.emplace_back(ZERO); + discriminants.emplace_back(ZERO); + discriminants.emplace_back(ZERO); + discriminants.emplace_back(ZERO); + discriminants.emplace_back(ONE); + + block1.reset(new block_variable(pb, { + discriminants, + rho, + pb_variable_array(sk.begin(), sk.begin() + 248) + }, "")); + + intermediate.reset(new digest_variable(pb, 256, "")); + + hasher1.reset(new sha256_compression_function_gadget( + pb, + IV, + block1->bits, + *intermediate, + "")); + + pb_variable_array length_padding = + from_bits({ + // padding + 1,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,1,0, + 1,0,1,0,1,0,0,0 + }, ZERO); + + block2.reset(new block_variable(pb, { + pb_variable_array(sk.begin() + 248, sk.end()), + addr, + length_padding + }, "")); + + pb_linear_combination_array IV2(intermediate->bits); + + hasher2.reset(new sha256_compression_function_gadget( + pb, + IV2, + block2->bits, + *result, + "")); + } + + void generate_r1cs_constraints() { + hasher1->generate_r1cs_constraints(); + hasher2->generate_r1cs_constraints(); + } + + void generate_r1cs_witness() { + hasher1->generate_r1cs_witness(); + hasher2->generate_r1cs_witness(); + } +}; + template class NoteCommitment : gadget { private: @@ -715,7 +835,7 @@ private: std::shared_ptr> cm_hasher; // Spend nullifier hasher - std::shared_ptr> nf_hasher; + std::shared_ptr> nf_hasher; // Merkle tree lookup std::shared_ptr> merkle_lookup; @@ -724,9 +844,11 @@ public: // The anchor of the tree std::shared_ptr> anchor; - // SHA256(0x01 | rho) + // SHA256(0x01 | rho | sk | addr) std::shared_ptr> spend_nullifier; + std::shared_ptr> addr; + UnshieldingCircuit(protoboard &pb) : gadget(pb) { // Inputs { @@ -739,6 +861,9 @@ public: anchor.reset(new digest_variable(pb, 256, "")); zk_unpacked_inputs.insert(zk_unpacked_inputs.end(), anchor->bits.begin(), anchor->bits.end()); + addr.reset(new digest_variable(pb, 160, "")); + zk_unpacked_inputs.insert(zk_unpacked_inputs.end(), addr->bits.begin(), addr->bits.end()); + value.allocate(pb, 64, ""); zk_unpacked_inputs.insert(zk_unpacked_inputs.end(), value.begin(), value.end()); @@ -762,7 +887,7 @@ public: key_hasher.reset(new KeyHasher(pb, ZERO, sk->bits, pk)); cm_hasher.reset(new NoteCommitment(pb, ZERO, rho->bits, pk->bits, value, cm)); - nf_hasher.reset(new SpendNullifier(pb, ZERO, rho->bits, sk->bits, spend_nullifier)); + nf_hasher.reset(new SpendNullifierAuthenticated(pb, ZERO, rho->bits, sk->bits, addr->bits, spend_nullifier)); auto test = ONE; merkle_lookup.reset(new merkle_tree_gadget(pb, *cm, *anchor, test)); } @@ -773,6 +898,7 @@ public: rho->generate_r1cs_constraints(); sk->generate_r1cs_constraints(); + addr->generate_r1cs_constraints(); key_hasher->generate_r1cs_constraints(); cm_hasher->generate_r1cs_constraints(); @@ -783,6 +909,7 @@ public: void generate_r1cs_witness( const std::vector& witness_rho, const std::vector& witness_sk, + const std::vector& witness_addr, uint64_t witness_value, size_t path_index, const std::vector>& authentication_path @@ -799,6 +926,11 @@ public: convertBytesVectorToVector(witness_sk) ); + addr->bits.fill_with_bits( + this->pb, + convertBytesVectorToVector(witness_addr) + ); + value.fill_with_bits( this->pb, uint64_to_bool_vector(witness_value) @@ -815,16 +947,19 @@ public: static r1cs_primary_input witness_map( const std::vector &witness_nf, const std::vector &witness_anchor, + const std::vector &witness_addr, uint64_t witness_value ) { std::vector verify_inputs; std::vector nf_bits = convertBytesVectorToVector(witness_nf); std::vector anchor_bits = convertBytesVectorToVector(witness_anchor); + std::vector addr_bits = convertBytesVectorToVector(witness_addr); std::vector value_bits = uint64_to_bool_vector(witness_value); verify_inputs.insert(verify_inputs.end(), nf_bits.begin(), nf_bits.end()); verify_inputs.insert(verify_inputs.end(), anchor_bits.begin(), anchor_bits.end()); + verify_inputs.insert(verify_inputs.end(), addr_bits.begin(), addr_bits.end()); verify_inputs.insert(verify_inputs.end(), value_bits.begin(), value_bits.end()); assert(verify_inputs.size() == verifying_input_bit_size()); @@ -842,6 +977,7 @@ public: acc += 256; // the nullifier acc += 256; // the anchor + acc += 160; // the address acc += 64; // the value of the note return acc; @@ -990,7 +1126,7 @@ public: enforce_input_1.allocate(pb); enforce_input_2.allocate(pb); - + merkle_lookup_1.reset(new merkle_tree_gadget(pb, *input_cm_1, *anchor, enforce_input_1)); merkle_lookup_2.reset(new merkle_tree_gadget(pb, *input_cm_2, *anchor, enforce_input_2)); diff --git a/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/impl.tcc b/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/impl.tcc index 2a1800aabe..d01b63633e 100644 --- a/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/impl.tcc +++ b/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/impl.tcc @@ -107,11 +107,13 @@ bool zsl_verify_unshielding( void *proof_ptr, void *spend_nf_ptr, void *rt_ptr, + void *addr_ptr, uint64_t value ) { unsigned char *spend_nf = reinterpret_cast(spend_nf_ptr); unsigned char *rt = reinterpret_cast(rt_ptr); + unsigned char *addr = reinterpret_cast(addr_ptr); unsigned char *proof = reinterpret_cast(proof_ptr); std::vector proof_v(proof, proof+584); @@ -131,6 +133,7 @@ bool zsl_verify_unshielding( auto witness_map = UnshieldingCircuit::witness_map( std::vector(spend_nf, spend_nf+32), std::vector(rt, rt+32), + std::vector(addr, addr+20), value ); @@ -146,7 +149,8 @@ bool zsl_verify_unshielding( void zsl_prove_unshielding( void *rho_ptr, - void *pk_ptr, + void *sk_ptr, + void *addr_ptr, uint64_t value, uint64_t tree_position, void *authentication_path_ptr, @@ -154,7 +158,8 @@ void zsl_prove_unshielding( ) { unsigned char *rho = reinterpret_cast(rho_ptr); - unsigned char *pk = reinterpret_cast(pk_ptr); + unsigned char *sk = reinterpret_cast(sk_ptr); + unsigned char *addr = reinterpret_cast(addr_ptr); unsigned char *output_proof = reinterpret_cast(output_proof_ptr); unsigned char *authentication_path = reinterpret_cast(authentication_path_ptr); @@ -171,7 +176,8 @@ void zsl_prove_unshielding( g.generate_r1cs_witness( std::vector(rho, rho + 32), - std::vector(pk, pk + 32), + std::vector(sk, sk + 32), + std::vector(addr, addr + 20), value, tree_position, auth_path From 359266a4969e68af3ac4f52afb667d713c17f6e7 Mon Sep 17 00:00:00 2001 From: Benjamin Diamond <30356252+benediamond@users.noreply.github.com> Date: Sat, 18 Aug 2018 16:04:55 -0400 Subject: [PATCH 6/8] add snark files --- .../jpmorganchase/zsl-q/zsl-golang/zsl/snark/snark.go | 5 ++++- .../jpmorganchase/zsl-q/zsl-golang/zsl/snark/zsl.h | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/snark.go b/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/snark.go index b800ab869f..a39d504821 100644 --- a/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/snark.go +++ b/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/snark.go @@ -125,6 +125,7 @@ func VerifyShielding(proof [584]byte, send_nf [32]byte, cm [32]byte, value uint6 func ProveUnshielding(rho [32]byte, sk [32]byte, + addr [20]byte, value uint64, tree_position uint64, authentication_path [29][32]byte) [584]byte { @@ -132,6 +133,7 @@ func ProveUnshielding(rho [32]byte, C.zsl_prove_unshielding(unsafe.Pointer(&rho[0]), unsafe.Pointer(&sk[0]), + unsafe.Pointer(&addr[0]), C.uint64_t(value), C.uint64_t(tree_position), unsafe.Pointer(&authentication_path[0][0]), @@ -140,10 +142,11 @@ func ProveUnshielding(rho [32]byte, return proof_buf } -func VerifyUnshielding(proof [584]byte, spend_nf [32]byte, rt [32]byte, value uint64) bool { +func VerifyUnshielding(proof [584]byte, spend_nf [32]byte, rt [32]byte, addr [20]byte, value uint64) bool { ret := C.zsl_verify_unshielding(unsafe.Pointer(&proof[0]), unsafe.Pointer(&spend_nf[0]), unsafe.Pointer(&rt[0]), + unsafe.Pointer(&addr[0]), C.uint64_t(value)) if ret { diff --git a/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/zsl.h b/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/zsl.h index 524e0b250f..eb08ac4df9 100644 --- a/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/zsl.h +++ b/vendor/github.com/jpmorganchase/zsl-q/zsl-golang/zsl/snark/zsl.h @@ -15,7 +15,7 @@ #ifndef _ZSL_H_ #define _ZSL_H_ -#include +#include #include #include @@ -41,6 +41,7 @@ extern "C" { void zsl_prove_unshielding( void *rho, void *sk, + void *addr, uint64_t value, uint64_t tree_position, void *authentication_path, @@ -49,6 +50,7 @@ extern "C" { bool zsl_verify_unshielding( void *proof_ptr, void *spend_nf_ptr, + void *addr_ptr, void *rt_ptr, uint64_t value ); From f752904a4b9c68183f0fbbdcc1584f510d52bf97 Mon Sep 17 00:00:00 2001 From: Benjamin Diamond <30356252+benediamond@users.noreply.github.com> Date: Sat, 18 Aug 2018 16:31:34 -0400 Subject: [PATCH 7/8] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22f47d2ee0..3ff4e2ecef 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ We redefine a shielded note's _spend nullifier_ as `spend_nf = SHA-256(0x01 | rh No adversary, moreover, can forge a proof using the prover's broadcasted transaction. Its payload reveals, by construction, nothing about the hidden witnesses `rho` and `sk`; the security of this scheme, therefore, reduces to the difficulty of constructing from scratch the ("curried") SHA-256 preimages `rho` and `sk`. (The adversary could of course generate new values `rho` and `sk` and broadcast an altered spend nullifier; however, in this case note commitment integrity would fail.) -This repository contains complete code, including precompiled contracts. The required minor modifications to the ZSL contracts can be found at [benediamond/zsl-q](https://github.com/benediamond/zsl-q). +This repository contains complete code, including precompiled contracts. The required minor modifications to the ZSL contracts can be found at [benediamond/zsl-q](https://github.com/benediamond/zsl-q). A modified `ztracker.js` file (including re-compiled contract ABIs and bytecodes) can be found [here](https://github.com/benediamond/quorum-examples/blob/zsl_patch/examples/7nodes/ztracker.js). Note finally that the parameters `unshielding.pk` and `unshielding.vk` must be freshly generated, following the instructions [here](https://github.com/jpmorganchase/zsl-q). # Demonstration From 8c4c8782885d31edbb6ef3035a6386785ab662f8 Mon Sep 17 00:00:00 2001 From: Benjamin Diamond <30356252+benediamond@users.noreply.github.com> Date: Mon, 17 Dec 2018 11:02:53 -0500 Subject: [PATCH 8/8] nuked "patch explanation" from README The precise description of the patch has been removed from the main-page README. A brief description of it has been added to the [ZSL Wiki](https://github.com/jpmorganchase/quorum/wiki/ZSL), where it belongs. --- README.md | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/README.md b/README.md index 3ff4e2ecef..93a229f5d0 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,3 @@ -# NOTE - -This branch patches a vulnerability described in the [Quorum ZSL Wiki](https://github.com/jpmorganchase/quorum/wiki/ZSL) and exploited in the [zsl_adversary](https://github.com/benediamond/quorum/tree/zsl_adversary) branch of this repository. In this branch, no `unshield` transaction can be mis-appropriated, even if an adversary intercepts, re-signs, and re-broadcasts its payload. - -The solution involves introducing a cryptographic link between each `unshield` proof and its prover's Ethereum account. Previously, an unshielder broadcasted a proof demonstrating that, given a public _committment_ `cm` (established by a prior `shield` transaction), a _spend nullifier_ `spend_nf`, and a _value_ `val`, the prover knows a _secret random nonce_ `rho` and a _spending key_ `sk` for which `spend_nf = SHA-256(0x01 | rho | sk)` and `cm = SHA-256(rho | pk | val)` (where `pk = SHA-256(sk)`). - -This proof, of course, asserts nothing about the _Ethereum_ identity of the prover, and should in fact be considered a secret: a malicious Raft leader, upon rebroadcasting the proof as his own, may steal its corresponding funds. - -We redefine a shielded note's _spend nullifier_ as `spend_nf = SHA-256(0x01 | rho | sk | addr)`, where `addr` is the _public Ethereum address of the prover_. A prover, then, demonstrates in zero knowledge that he knows `rho`, `sk` for which the public `spend_nf = SHA-256(0x01 | rho | sk | addr)`. The on-chain verifier verifies this proof by supplying `msg.sender` as `addr`. Under expected usage, the proof will authenticate; if `msg.sender` differs from the address supplied by the prover during proof generation, the verification will fail. - -No adversary, moreover, can forge a proof using the prover's broadcasted transaction. Its payload reveals, by construction, nothing about the hidden witnesses `rho` and `sk`; the security of this scheme, therefore, reduces to the difficulty of constructing from scratch the ("curried") SHA-256 preimages `rho` and `sk`. (The adversary could of course generate new values `rho` and `sk` and broadcast an altered spend nullifier; however, in this case note commitment integrity would fail.) - -This repository contains complete code, including precompiled contracts. The required minor modifications to the ZSL contracts can be found at [benediamond/zsl-q](https://github.com/benediamond/zsl-q). A modified `ztracker.js` file (including re-compiled contract ABIs and bytecodes) can be found [here](https://github.com/benediamond/quorum-examples/blob/zsl_patch/examples/7nodes/ztracker.js). Note finally that the parameters `unshielding.pk` and `unshielding.vk` must be freshly generated, following the instructions [here](https://github.com/jpmorganchase/zsl-q). - -# Demonstration - -The instructions at `zsl_adversary`'s [README](https://github.com/benediamond/quorum/blob/zsl_adversary/README.md) should be followed exactly as before, except for the unshielding proof generation step: - -```javascript -result = zsl.createUnshielding(rho, sk, value, treeIndex, authPath) -``` -should be replaced with -```javascript -result = zsl.createUnshielding(rho, sk, eth.accounts[0], value, treeIndex, authPath) -``` -The call to `ztoken.unshield`, on the other hand, proceeds as before. Notice that a replay of the `unshield`ing transaction will fail to credit the adversary's balance; a properly authorized `unshield` will succeed. - -A general unshielding test suite can be accessed through `zsl.debugUnshielding()`. - # Quorum Quorum is an Ethereum-based distributed ledger protocol with transaction/contract privacy and a new consensus mechanism.