Merge #515: [0.17] Cleaned up CA

ac642808a Set correct ports for Liquid network params (Steven Roose)
6491b4da5 Use upstream Shuffle which doesn't cause DEBUG panic (Gregory Sanders)
de56cd82b move fee estimation test to extended tests, takes too long (Gregory Sanders)
1f062da26 Fix compilation of certain builds (Gregory Sanders)
7d86c7d97 remove leftover asset debug printing (Gregory Sanders)
1fec77f46 Add elements transaction unit test data to EXTRA_DIST (Gregory Sanders)
665d9ebe0 Whitespace and tab linting (Gregory Sanders)
30552459d Spelling linter (Gregory Sanders)
0148024dd Functional test linting (Gregory Sanders)
242515fd6 LogPrints need \n (Gregory Sanders)
b4c9ccb8f remove duplicate includes in test_bitcoin (Gregory Sanders)
8371cfcda Fixup blind.h guard (Gregory Sanders)
bc9a6104a Fixup wallet log print (Gregory Sanders)
f56de12cd Remove all but one circular dep: coinselection <-> wallet (Gregory Sanders)
5c3481a91 Lint RPC args (Gregory Sanders)
04a8f2e05 Document blindedprefix arg (Gregory Sanders)
a687dd9a3 Add assets and issuance functional tests (Steven Roose)
96af43b18 Add confidential transactions functional test (Steven Roose)
39020cdc7 Adapt existing functional tests to CA (Steven Roose)
6113bf89c [BROKEN] Add fee outputs to functional tests (Steven Roose)
b48aea828 [BROKEN] Account for assets in all balance inspection in tests (Steven Roose)
43f8b459d [BROKEN] Adapt bitcoin_functional tests to CA (Steven Roose)
5f9f578ee [BROKEN] Unbreak Qt code after CA (Steven Roose)
f7920d77e [BROKEN] Add issuance wallet RPCs (Steven Roose)
a361dda7a [BROKEN] Add CA wallet RPCs (Steven Roose)
b6397d695 [BROKEN] Adapt wallet unit tests to CA (Steven Roose)
d3ab44de4 [BROKEN] Adapt wallet to CA (Steven Roose)
49e808373 [BROKEN] Add raw issuance RPCs (Steven Roose)
a03239dd3 [BROKEN] Add raw tx RPCs for CA (Steven Roose)
6038c37c7 [BROKEN] Add unit test for blinding logic (Steven Roose)
aa33202bd [BROKEN] Add blinding logic (Steven Roose)
cd5662664 [BROKEN] Adapt existing unit tests to CA (Steven Roose)
f4553d4b6 [BROKEN] Adapt existing benchmarks to CA (Steven Roose)
703db5b42 [BROKEN] Adapt existing RPCs to CA (Steven Roose)
6493ad902 Adapt bitcoin-tx to CA (Steven Roose)
0b5066143 [BROKEN] Add CA validation (Steven Roose)
d53479c9f [BROKEN] Introduce CA in data structures (Steven Roose)
04d06917d Rename con_elementswitness to con_elementsmode (Steven Roose)
484d83c37 Add new arguments (Steven Roose)
fde9fe101 Add basic issuance boilerplate (Steven Roose)
f81122f94 Add asset and value blinding functionality (Steven Roose)
8b8256d9f Add ConfidentialCommitment-based types (Steven Roose)
d0322f7f7 Add initial issuance boilerplate (Steven Roose)
fd411bb17 Enable surjection proof module in secp256k1-zkp (Steven Roose)
84954c811 Add CAssetsDir (Steven Roose)
10773c6c1 Add CAsset and CAmountMap (Steven Roose)
14af0b653 Implement blech32 addresses, expose over RPC (Gregory Sanders)
0f2878036 Add blech32 implementation and API (Steven Roose)
9cb2fa051 Add blech32 python implementation with diff annotation with segwit_addr.py (Gregory Sanders)
17a22f149 Add support for blinded addresses (Steven Roose)
00fba7771 Add liquidv1 chainparams (Gregory Sanders)
045e61569 Add CKey::ECDH (Steven Roose)
aa375e66c Replace CScriptID and CKeyID in CTxDestination with dedicated type (Gregory Sanders)
3e7cff1e4 Disable tests that are not relevant for Elements (Steven Roose)
3a0b3b70a Add tweakfedpeg and compatbility test (Gregory Sanders)
36a5d5d0f Fix bug in calculate_contract for Liquid (Steven Roose)
77ba5eec4 RPC params convertion cleanup (Steven Roose)
ce19a5f6c Add secure reporting document, link in README (Gregory Sanders)

Pull request description:

  Cleaned up version of https://github.com/ElementsProject/elements/pull/503.

Tree-SHA512: fb82a182a49384dd4be5838c147f6049f3b554f6d861681df46d83819c39c460b469cb68a8f6c47a2e8d3c17e439fea8818f8bca03b200027e6a127875376892
This commit is contained in:
Steven Roose 2019-03-20 22:56:33 +00:00
commit e9f6e82bbf
No known key found for this signature in database
GPG key ID: 7FC91380BB4CE800
198 changed files with 11649 additions and 1931 deletions

View file

@ -269,6 +269,8 @@ EXTRA_DIST += \
test/util/data/txcreatesignv1.hex \
test/util/data/txcreatesignv1.json \
test/util/data/txcreatesignv2.hex \
test/util/data/txcreatemultisig4_elements.hex \
test/util/data/txcreatemultisig4_elements.json \
test/util/rpcauth-test.py
CLEANFILES = $(OSX_DMG) $(BITCOIN_WIN_INSTALLER)

View file

@ -74,3 +74,7 @@ Translations are periodically pulled from Transifex and merged into the git repo
pull from Transifex would automatically overwrite them again.
Translators should also subscribe to the [mailing list](https://groups.google.com/forum/#!forum/bitcoin-translators).
Secure Reporting
------------------
See [our vulnerability reporting guide](SECURITY.md)

894
SECURITY.md Normal file
View file

@ -0,0 +1,894 @@
# Security
The maintainers of Elements take security very seriously and are committed to addressing any disclosed security vulnerabilities quickly and carefully. If you find a security vulnerability, please report it to us following the steps described here.
## Reporting a Vulnerability
Privately and confidentially send us a description of the vulnerability that you have discovered using an encrypted and authenticated channel. PGP encrypted email is preferred. Our contact information is given below.
In your report, please include as much information as you can, including:
* a description of the vulnerability and how it could be exploited
* its potential impact (e.g. privacy leak, denial of service, theft of funds)
* steps or code for reproducing it
* a proposed patch for remedying it
Also, provide us with a secure means to contact you with any follow up questions we might have.
## Considerations
Please take care not to violate the privacy of users in your report. For example, stack traces or exploit scripts sent to us should never contain private keys or personally identifiable information.
Give us at least one week to investigate the vulnerability you found and up to 90 days to fix it. Also, please give us reasonable advanced notice if at any point you intend to disclose the vulnerability to anyone else.
In general, please investigate and report bugs in a way that makes a reasonable, good faith effort not to be disruptive or harmful to us, this software's users, or the users of dependent projects.
We will take care to inform the maintainers of dependent projects.
## How to Contact Us
Greg Sanders
gsanders@blockstream.com
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1
mQINBFWn84sBEADrLv0m7garpyq7m8e6g52JaW2RqpkAHt5PARe5BFj1nrAz5FGY
eAMWEBEKZOnUCf7hwYZv4PCuWi8Z56QNI3MaxILAAxDG2OyfRvdCnVTFm5H8DNmQ
juiW6GYOa9BzKGwrBxDwspIIeo96qjcmHKZyqusoEfuJi48X+vxCVlOxM6OYtUnb
H7FeCDYUuNUrruuhNFgidf0bo3g7NxOmzAfEQQ/gvlGJIHgIRwIyrfMi8NiOy7tG
9pEW3kesCijYwzkI8d5gcodPCoGQ73iS9nCLpg+FGF3d8+Lt61xt3YrLTgbxfUb2
4id7vbCuHVnoQSKEL1c7X8Fd39QW82S78+hxjIci1WdJTQSCpnAYS6U9kfKDE9tv
ZJcm/TO3F7hCiH1XUbBn9zmQVp1njgtaKLM4Vsq8l4d+M/FQYZVnRIN3Ohjsz7Xw
gL1wSMWYxRnGzFjAVTJsJPdXlCGMeX03zolyPal5K6o3Uw9xQE39cN3zlxH86vP3
LuwHLvIUKLSDtqS91JI2sVxZwdwwyKtxcld/H1JEMv5hHJ8/ZrIDy0Brohl0VrC2
obienxmYr8K7WieyVaQdGZ4S9/HyX8cZW/YLBNA1oMwa4Lcgo4GZMYsC3+w9PSAw
Q5NoDWDOuZy7eHlyjUWGWmgqWXFc/EAR5fdB41Ji0V9XpZI8Dk2zHlJpMwARAQAB
tCZHcmVnb3J5IFNhbmRlcnMgPGdzYW5kZXJzODdAZ21haWwuY29tPokCOgQTAQIA
JAIbAwIeAQIXgAIZAQUCV0cVpwULCQgHAwUVCgkICwUWAgMBAAAKCRBr4s7RSpkX
vOMYD/4jkg7juqKpg3i+UuP9sa6WQ0RFhAofNurHcC3YkmPG7XtgboRKsEKZh4yQ
ZoKRd6JqDaZ8ukfW0xiGWmhanTYlcEUpG/KXcQGpZe9wTTBjOLAx5zO0CqIhP87B
hiXGW2RP5j5JyQJITOutmxSVdr+GuyMq5rAzBY/a+OmYoN01pJlrt2O4jELovM92
9YJY/oh7RFEkSG9F+zUE27vR9jbc2QwqnTpNVWx1qkuMKJIHHoDet0dJX/xwjx6d
tMaCNucYTPcEwZ2jzRgYdV/D1N0KGWsLiWNDg/9YxwPXgL8nHLQBjLEDm56fKqu7
ZPtn8P77uNaSAkxviVY4Dh7P59Nj9u78C3Aiumynr5fpHUPlrX1a5bgGRT/UkIZY
JdkPteC4QCgF9q+Gi5bwyXuIJJ2bVO4kwmdWYxQeJ7TSxJQjo1fNacHZ9JLr5GoG
IPkmqrqEg9pU883MW2r8yIKLfdLZvz7CM/TV9OfNCAnpqFZ1OOmn3XHdmRWZ10UV
FsM0LdLLqMtN5vxRqQEBYIc+XySX873ynL2R2/P8LLpQkdxTaPxlTzaXB6pwT75i
HP4uQ7VRSSRbok0seto2J/H+Pf37OPubhRFeLnOYU5QsGRvDRX4wePC8feYUtxD9
fs/Wy6lyG7Tu7cy6lCwl8Rm+QbBwfG2brDxTL57iH8BJ6yZhKIkBHAQTAQgABgUC
Vf2XiAAKCRBPdE2h9fFTgQZwCACUDs14VONs8RgL16UBrtpRwnps4qxE2Q2T00xb
8GW2SipECGr+SLmX6yx/FcL6SM2c9D0kSrZl4IaCjyQn99UG7sTjvnyj+LKAb0BT
fLCxco9Y57CXhq07MoS9i9b15VPBN7MVU4h4sShrHKZDOPkvimkQhNCZcgM6RgW2
lOQQuwIVPR1P1X/1cGmvt0GCsVW6/wxCe+g0vB3He28YoX6oIo6iZLrSv8XRLyWu
jYkQHUGd3h8vv0M47XZPsZmSq3SRJrfOWBl1Uz7SkL/7orGvmdcXrxtccx+/tSV0
NVbn/glCAB8n/Pqb9iv7GAmvAtruNl+8y8JP/SAGTmLYcmrviEYEEhEIAAYFAlX9
w5QACgkQrIWTYrBBO/qfYACgh27wC+yQtDW16pi2NNxSCY6meQsAoIyrRc/YkVZ2
HxuQJyd9jnlyCiAhiQIcBBABCAAGBQJWCoHvAAoJELGnDk+NzQNmIwsQAKPryApg
a8wy9CGkiQaw/YIzRXBYiUKyoEDmC5XYvQ+wK95pVW1xZSXsF0W0qkmRpt9m3mfe
feGiFo3dGStjySyGDNtINzI7UE+rq86XUkiezwR2mQ9Qn0NxXmbQ3ix9PZKv4SEj
6JOv4Dg8uJ/v36DexY04Sp/vZ9iyakR+XaExJbZ6UBQzAUkmWVqEfWjljU5KqHRq
h97jeFvivqO9Nyip1mWZ0ZkCmSQwgDIR8u23HpxESrqY80jYsrBaCsKuDdehCNjo
Mo2og85gPC6sU9/gccZlBZed0vjJuKAG7HfCUEYeFZIQDi+Fsrfl2qyV4207uxXO
GN44m9Pff4T/5k8pnfOQwlMYa8ThL/3z4kyQ3nufltuiFp0Y3pk3neSPWEyZgiXM
YD5bdDPN2LL0qgNausHRd4Aj07irMhILD82A9yq7bW9kLYDNOAttB2uDJxgzpavI
BiSwQp7kgV4MXSUTyDIO6ouHYT7vjSfn07FO3G82ADBzx+iyiOlQu9WZTRRfEhZn
fpgx5kT/kyupc/x9WrKmAKU3sK6ClovDaCDFnhjm8cVos16a3SCpVqxXEyXv+10R
ZY3nUGN7bDKuYdwUE//JzdrhcjsZ+bFxrYUFdEM+rm7DvJc2UnOtbzboVNpkazwH
d4MDnNQpjaOtTF38R+uKZeislzvK2aB8053XiQEcBBABAgAGBQJXRbMeAAoJEJhP
EMx3Fp/Sqe4H/2zVVARVrWz105LMc1V/Eispq+mBDwxqfuccTDtfLDdaesjNOz9A
KrRataTGfy1Ioc4BMETdOixs6TemiL/bhA6+5Gbj2t5cLjt2E+4TFVpE5mtbHlJw
IvqVcKzwpKSGkz5yYdMZo73iRnGiYoEorfPj7Wl67d0suzgt7aAaDZgqmKTushWI
PbIfA0TgSBhck0bBXMG76oioNoJtJ5hoc6/M1v4+xOBcgv2Qjs/1Ez+G+ScZspCs
1gC+YigwKL2Elkx2DrJHoKcjJRVXOmfryiwKCOJzsXUZNUOtSI59YSI6MH7efMg+
tZyEb2aZRh/1F15WVhaSMK+syfdya3CbNwKJARwEEAEKAAYFAldEkKoACgkQdIEL
ASNGyaZP+Af/f255kB2bXMPqxxiK2a4noaQhWo0LnLMX7geVYOMcbvsZFMq0rZ1l
16XUJxl7kCog2m9QoElDHOMpZSMJXcyFsWSEgObRZPrgip50u/nevKqoMUT7kMGZ
xBeq7478gfh3FZTcoP4s9pY/8riVuyjwKbxfBxBVzxx3ASAMU7VsKGcmwbi59iLY
WcYZWUbT79rW5Yts2gxPD74m2txHhocgVs9uTrwCH3O8GWKlo/jrA/wqRPlN9EYs
EFXrjZ4DmJjLs3fMI43HN6VaVz1paS+kI0s0+CWV4hiGeCLfB2h9cMYOaoGCn/90
z0sypkdsiUyReInrHQ10T0yjzstxHQ6cOYkCHAQQAQIABgUCV0HiswAKCRCGD+uA
TmaTIFn4D/469kAgZgXTgcIDgX1MKPnxXcPxjfllmyoBsDOuYXk97YoBo4n55nvT
kv6cHR1wbISg8Y/OsFK1AABpvbOgJ7Kp+a3P1yXdoFZ08RDl2Frffe5BXEQ8EC+1
o4eCtsgjHBjUscX3dJHGaqXorIocJ8csugJYoE9+0A7ZUB8XS0RXH6kZ3lLge8Bj
zYkXUNhzG1gjlbDlZM6tvKZKowUQbvkra6IO9jZhTksWWsmEsnhlpA1t6BsiRxNH
VMf0CdltirBr1JD50zxnc2panOBJWOCCSjJV08eIv2zTCMW0r3y1Cf6EK2dtsWI+
JE32DVk14bATPekCeRyFgVONBHgn6vBIemvgUYrkEF+EDNLAye+s4domOfeupmZj
MJ+Y2LUOSRLRoArEzScznoiHuNI9zz60fjbe8clxesXCEXA5VAzhH/v12EW2e8hX
Y4CEc85YwngP/osWm072+jE9aPa/uvUYB5a4zG8fDlu7yrV/8V15COtnJFR/ZDet
NIQc719+j7plPW1EgHAwwF+JkZiuZsr8R9dwAOSliq/cQkV5pXWOePt/l8QeBsVB
M3nNmXqv1oQ+BxZrdM484v/UTLqBJVhRtZpftOsWIspLT4LBHMubdIrirtzVm0w2
vj+zzmYhCVixtDf2RflEtQBWIQGZ/isH/yxBBkaVO6J8K8Wsws1MR4kCHAQQAQoA
BgUCV0XbegAKCRDFJCoas5NlFzUUEACax+m1K7/bHPtcccGVIvoPg2WtM46sMFEN
KxwwViHRu8hDVtPCO7VMWGiWUjI+MF7upE7hg1l/us5851aI7Ho7tvidcLPdl3yl
f++F32ay4O8dIUHiNnDDYcHrrlhxFkWxfCE8hHIu36uk3rNXALLdNwfzJZr18TtK
052QLGr8xg4eUdfVnr5SF7oQ8cfMCcldJ9vXXkC6t0JdsNeCr9Haawv0fv8FFYsE
T4OqJSEwn23x9sRkyDAwv5BshS4GD9VGlwp8S21i88OoPPTzhf3T6A7KVIxD0B1Z
3dqKFw5INTx7hmNRlWjYXrZhUxCLkcNMEAoVzMYMEpqkgJ7AroIeHoZfUfCqyrDN
OekMxMZqI8E3br2ObaBTpNj2WJGP9bSSa1hREblrvxHyOo/J0SlU0KSLVnvbW+Fy
hzkKkancs/Lv1uPdwVlgxyY7xHRJG+IVudpw/kXQv1EReBaKKuPMDDsCJdwmKCeX
ejEz4lSFQgpWCUSBnKtmBpMT+yvi8aciZQJ8lkIPXZkBka55BYMG1W9LH9RZK2gq
RA8mcr4ia6GyHcpLAHBGzkV1THVTi7nNpdljQufNninaTfYwAIvN5EMAr7IUnGc1
sPtC0m4HdOFc+dEVZYGEgIGjbvbfxm0d5haPfBTtW/N+f3c2E2KaO0uDy76Qpc7x
D2HB0ov6/okCHAQSAQoABgUCVz+OwwAKCRDAwHYTL/p2lbvvD/9n52hiAk2Fku29
Lso9jdRHsDVY+0no7vEQmqDQ8r/d8STVDEdK9BTh+jynB4XrBQLKgjRRj/e504Kc
v94aTeemhjngXof0gvzx6w3DAaa/v71gIVED9uyTxkX2/CJLhVy8NWe/Bs6VCbYA
kITxUMheWLeiuyLKevFVV19OohYyW1X4XBaBxmT08Pjuu0N1WR3zixbNabHq7VeO
FlttJwVCAg8EDhkPAZitqaITjbOfjLcfImDWickrW6yE9QcA0ljnoPJwXfQDcnc0
9TZB0pm0rq6R0L39zK9KXBcKWWYGo5uwwprKP4LVw2fAyfUbAXD0BLaSj6Pp0+lu
T2Iut2BdScvfYwfO7Xd2a7vQzUrBt2+NxfLAMoM5uFtpZ/Ip6zBvjqiyep2u/qUs
lo/++eGKsnI+yiHKnqXegfjBoap9ED6l5rO+qjRmuQXCd6oBfNkTtDhe+/0++0X3
QDZR1dPpitRGEA5vprBgdMkW1ayhsWkhUDSdlTUCpNb7Wor9SEoE80H67aafyADO
Ql9X3r3lQeKFI/a7yQrviQKExisgANQwpzImi0GbgHAP9Qp04abdw1lE3P/IWycB
c0wo/4CJUmMxGSMI4izE6wcETmVj6GuqIS0GUqKFt1EdJuNZfW2n7cUftclWahFb
eYtM0phYNQUSUN6cKyRzIroPkUswk4kCOAQTAQIAIgUCVf1qxgIbAwYLCQgHAwIG
FQgCCQoLBBYCAwECHgECF4AACgkQa+LO0UqZF7x/+Q//a8pa1FLLG/3aZXDv+FAb
pZTpcuwY+knAUD2waOk3ZFTpz15WykEAsbtCnIokAhdWqqvNb/6GdFNHT37dMbrP
I30OQdvDwXQcsCw4aRuQb9IEESS4MBP6AcmgfJjmH3lX88PsvFKgHIkm+1wgCwjB
fgIsHlo86GP8AoVOUULxwi0JFITuwikfCLUWzKDa5vyv5bOo6IHIZAk+qNQlQn7c
kQF28G6v1wBdMziwiHhlfqkxAe38SqOUAkZYe2oKQgI03EBuYCZ02p/CMTMK+BTO
2Jd/dw1NYQfVufASdTrYcZdp30GtWCFyIrSdBK1SY0m7V+k3G9wEMEB4tPgbhOSc
j1eG0WY/31D4QRnYHH33Fy+fugyFg5YcoCsHe4mRM8XNrTzNbXdreSjgM5TaHS3V
289PSScH+GerIZVIl9MZtmKY6WtMLGS0HSv4KDYCfraoNtOyufaWVB2jXna/Myfl
wKDEws65+lRNX3i6VHdfemUl3ucUNWPYYWR+9NjA2l6gKBvk0aA90dV2Ar5LImm3
DfQEq5n7Yd7gbmWpKfQQ23jvOikLqRdGnRp8PVRhPtp2EWwwC3+PJ3O2DXrveOLD
aoXnY4xqslY3fMOuQX5uiSpOGnP+yEbfsGFOhAQtFYk2b0PXgJeaxCaLNiieLfqS
NkZ/XjUgpEf9PSqMwaD4VEmJAhwEEAECAAYFAldLSFYACgkQNkiogvQxa5sC6g//
ZxTMy49UEOEWilk642NHM/9RJxZDHG6WkxrjEbdC3NDTlqzx/3KuBDRM3XpLZKdo
+3bgLo+/wgs/8FsC+6NgqTSd0+yEtafWHmnxnDgMlgoeBbkK1pak9BK9ieU4biQC
jZEvZh/dGdkP09lZDmt2xmRIP2MZIFDedH7C/3sJoxiaeQiVzbB7TwBCk/kMZwAB
LAoaJDrYKBBg36wtbracfM6EBq1Gfmw7096WV0uizlOKI9Q5mp+FTcCncIpyTkKh
JINV8A9kvTIqQRZxC1KVH8V9LSslqygEAuSBWMt0YVg/F1Tb/DtXr9Zw9FGC3AIC
+0jc2Y6eCUWygxedXPMLxyamd11ZfY26xg/cxUF1yrO+Pn7h4uorU6kybXY2OjRN
FNjHPVmSL7T9DuFVZZsQ2LGla+5gmzc9ob9kvi3jOZX4PlrHyMAM2+BGAZaOYqxu
V8C6IcFK2e4YxMz5p37eFrjjlJnUdV+3m113P/fCrJqBu1gJS0jaA8tRjWHZvPJX
3l72fG8uQEFARiRHNJ7pOgUHdm62jBDQpvbWkZbagzXHsZRqYQdjXgRT8aMeGLT1
8aT3mTwF+y249E5lK1fDxqKL0oYSZz8ixIbEOqZ00j8lca9LcZexflJQnXOHt5wD
LU4F08Z2wtun6u1C2qL1wF074ZbO2id8G4Cu4k3LZQGJARwEEAECAAYFAlfwQ0kA
CgkQf6sRQmfk+gRKGwf/fzo2rNkbNuIypxkYjOHhrdJKWO5uFYujejkFGI0b6TBa
FX0SUg29A+fGEtLLQZoJJ4uuL5cTkcol4DwoK2oE0USXV3Y92zDuZ4kUEt9zNlGf
shzoj6IVPXDP8Pt61/BF9LMrfdLWkFG5rFnJ3HOJl+cnRPhHnEKjRJ1a0cRWwdvx
/+j4hC+ic1dmvl90/YyBiAiSroTK5niYWUxCtOMWYb37JMxkpsUjcHpeEJ8rKFlV
B+v5JnbxQf93v6bfvwho3OPGnZ8ccxIKYmmZeXM6PW/RCSy86Q9wYNOrpS8/wQm4
/BXfH4jznItUh6PRFNlFb8JGRJCmw6D1JevqfQUsJokCHAQQAQgABgUCWZcyiAAK
CRAXVlcy4I5eQSapD/4x5FQzGPCJdwyfqjyy09XCND/iwzLguHwcQw83V/6p7/84
IxEbHFvyaU35XCiifp+Pnna9nRxKnX7yqKdTtquWvEHZelJtMgNnqZw7hZ9QNo5Z
3/SyStroj0jrmyV65Fqzpcn9bT6EIj7J7RwU/xb9lAqt77W+cad6YxhBoPTofy1u
uQnm3fK/D5jqydOjydtVhBOf3cx3NLi0iQy1wG3KJLqXZml6/nVeifybDG9fR7Yk
CCuwXpLGsY0cKChz4DdVNnWYOdE+uXoD4aWCxqGZpVXicQxoo6MgUbA+7dkFBU6F
Lg25ZAXQ2uFvj/sg6fXcJyZID6P9Cj3gExEZ3g1OS2fBdN3Pab9LKqjQfFRVKrn/
VhHYji1vDlGLA6Q7jaosl09p3OWcKNeOaiD6nL2o0gweDYF8BE+wvh1XpNmiVLYn
n/h2ZTMtz7vb/f1g1TPbKGYOJhqWjlnmEPRr7ryc/ZVD28w/69UKCmmclgQhlgXv
ftOZ4KbPX8WAjVUHG6enUsuxE5Yj/bFBgpQryngBH3lw6f5mOaPnQUVZ2Hn7Iwv9
KdOd6GhqiSwI3t0ZXOboiu6VRgYrgbeWMd8VHdfilPiISHQdPvkKJq9eTJXEY5ef
LalUY4geril11foJUYPez5drI7FonzZLwkUqXUYl6dX4m2ueWM49JnmhVA11uokC
MwQQAQgAHRYhBDX0raYj65/jo7x+9nugNcpbkBcTBQJaoDV4AAoJEHugNcpbkBcT
a7IP/1fhVYb06JC28jmzk1EsQDy/nDyyNXL58xpydM7HQG9KlWT7Cpa5KPSSNb+7
ifPaXPse34FNdu5xj6/5H5+G04Olq9smfMfRoD/ziNvqRdu4+SxSbqjCy2LgAx3B
m0SO92v9vGG2RDJITZIdKJX77SNeluLMn1Ktdj3nxXMmwI3qkqi1OvWSZBfs6JlK
HOb95I23/LSfH55SZ/X8qHVqJi1EKNaYy1r7XU1VsXUYn0x+q6QWJX/ZjJmSY9LL
0N24CsbwJSDQjHKAzyknB/BOepwwqTQWwLml6DOO6Xz4yBSBrDubJUFXiYIPgV69
UaDQeRen7NDi3mWj7FMtUT/ctHonbv/h+LZcfNZHsux1Ar0rJmmAOJoZ9jv3fZ5q
sGBhaAutKDBYE1+w/jKg4Q9wr+2qPTOftFU5Lbom1kPNgFIkv4Sy8dDxFUWFL3Sj
UwretmFe7LuxAasK4hO+1abQU1E56x/Ebvl/8wnhtJh9Ngbiv2zh2iMCQTrRDoJy
qEeF0K04zZu4oplRuZ+3p9g8RdYqwnJdL2O4A/Qt0t6C39L0JHqL8BIKAhL6u8kU
ZTpub7fBMv5VvQVXSB+uTPnWa2s0H+rLIvlZRB74DzLIZgh/W+llKlPUV9fxedBX
/2SjUMExtK9YInY7klDslu+bFdFeM2/WYnnPZH129iLP6jjdiQI5BBABCAAjFiEE
YGhbMKoYM6vJRiwoX+CNGjph/s0FAlqnItMFgweHcQAACgkQX+CNGjph/s2wJhAA
noA67vSOtkIu0IG0nSW6KXnGvRadDVZYhFnLZld8nVGlfdDhxW4XN6YXHAje4ws5
SfCtJRzigvVyZ1B4Y8ItnZsZmm/ijZPXidNqCCWa4sM55v4Qir4s9QzOu3eCMLwm
rdt5o4ug6GdKJKOv90EPTMxESKxe2Wtl9m+6EO4NHuyWbdsNcmfk+YlXykjg73x8
atCPkIeivtRRy6aMoPwxBGkDHqZySr2TtbOP5I7tkExYn3epCjLGtmkRer0BvSyz
b64QOeYA8FLGWlDauzmq0aamB+ZKw0EEniKVBS3yp6SYVSS92FNtqsOLkqwupPBc
js6TcNjNmtjZLvoI7TWqZZqqacn5oqLgwk+rs6R3bpGylDNvj8dd/e4LlAg2guK/
8GmSLoqyLq4GhF9sAaWJPU7M/YXYClLYmiBlKC5rONHSXLfopgFg0CfnU+1Qme+g
+kb0tQ6iUSFqYLDCTsjOBAZVt4uwaJPao8692b8D0ppfqZtyaW2gFo4kblsS5o0Q
4as8AL9qB6ukz7YFBCTz2t5YV6tXIkd6hGEHWa12VLyqiRg+Sq9Rq/srIVn96Xxf
ajcHxzC0FIRfpICDRPPj/t0sBJ4YiQG2giNDHHpLv80SADX0JnX+AJL+C1M2UxfW
EBXkCsjIrbVaJeSV8IZJYQL0nt2acrkOHDvXJJ+x906JAjMEEAEIAB0WIQSvkXMY
uMQtESchYl0VfvysvGSEIgUCWqc1DwAKCRAVfvysvGSEIsApD/9SJNUy4v4S1E1D
DvGV4E/sxlDqS2CW1CAajliXikP83pmyB32WyUwc4TbJYmVc8h6a0wnNxCEbUwMn
wU9H4ntiKcJBoSQgG8sJLfF1zQ0WYyUFwrWo0ZuD3WW2wTB9Mp89V4THNFqVrO3U
GSKb7OruR/+gjLMToYs1OdfSDuRIP3awXqoakfcRg7ep0QM+T/P4QwRjNPmEeWgo
U5025uKRiFDG3yGMEpbYTMJOwIJmnpFI3ZDH2XodI3yBmWIHnB5TvFt8FPPJdZ8L
rPNShPLYQXCJmMbNAI248sU0nTG4r8I3sGl5VdXwfRgjWxsBDFTAO82oo+txnmag
Sj12BeV9IvyAilnM5HyOis61Vj7E61FrTE6URYj8Utne7irpsz7gUgHoH2pEX5fR
MxnpEoESMiQaHkwnXjr6mXHo/+z2fzOkFcGdIeOb2oe0APmlag1aV/XwDDEYTUEr
dA058x7KewOHpnt8cGYw2Fb7saeye7pP2A2SlNE+nCHDNryqY9mzXC0a4P2b2yr6
Vn66QqXzxBmRZ7Uj/FpO3Q1+KfFhVxdA0ceFF+GwQY4PLT1IIs2/3AeGTkISL6J4
XCCTDQWiPHBou09g99xuNMhRxaGEPtZjVKIl8tNUX3fdHE7RV0OCVt8D0xDBm8mc
a4ZhDcF1hNiHvf3fETLyKg2G9F3++4kCHAQQAQIABgUCWqbx9QAKCRDTABFuHIda
PXbqD/wN30C4bLMSRBGTwtcBO+iT6kw8fVkVZS1T0BLlj+iYmEKvmbATlOg4gbkA
aMOF5kB/dg0PI4dXtYBpVB0+6P+3sO6iuulQANN5hJd+wbcK33w9NO/MCKE6iyuV
CHL8NeljA9FsGE/oBHEzvcI7sx/V/UKafZh6GFYX3TUx18ayu3FhQWVw4GI3rwu7
zTW3G1lKf9ctVA/XKlcuUAYqaWOrZnLfxpoLoHnPLIBbaDNWJ+zH9IlGZvV5Cr+V
9Ggg3VtwAJ0hMhJwvsi23BwNPKpSWa67MPWzDPYMmZrkwWbTq/bIR4WvCt4iewhi
eUHfMh1VOxBTxXMlZ9XkyB0Qfj+rKmy7T5G+jE4cLXnldj6o5u2U0oSjPQ7iQwUh
xBhYD087ZiEreHzB4EVl8sbnXz7zC0G5Z3RCJY4BJtHOtIOfnNdUWgbHNlmNAXv9
8Z1PoB/zxG/TNzepq+oGetPShucBpJTbW1V9DKodNj9SVm+OroyYA38EpRvBOKsz
5sRiDP/Ac/Tls8qWkE6eCU1ipusDNJfGTLC0cVjYA06xgjfVILuYuOlWKRvtd5Gl
8weo/O+uqj++JuxEXzHQVL8/cGnbNokF1v8bn0XVeXRDl1bwD4L+Dpft6Eniv+2t
4Hu3CB1Vqk8CCPfpDieggwxP3CNED6CNtmIjtF9pIAdWbbhkpIkCMwQQAQoAHRYh
BOQUqhIZ/VMY2Selhm/RX0sWRlDMBQJaptvMAAoJEG/RX0sWRlDMIuAQAMUM33wz
+1F00OtniQGq+RoD9nCpRBtrAdb7zKOuxCmfs6r+gobS4D8m8xFYLxHcIw4GGxVP
eHnmhmMOuY+4YaKE7mbIFfikZw5c4zE+0xfHrsNNRwkjrlywzPKaY4vbsehfFS6M
Q37SN4VbXsZJHslrtfkru0YkttRGhul5b7Yk8TT3cHNU5Imrk/0sDviqitSrgQQM
vMmg1H1+6+RFtmG9y9+JEJFRfSAafyHjfasnrseY1XIFA9FQMJSNqPOjMiC6tHd4
r/yYmz8J05UvVtC7GI114WKVSPqvub2vk0jsstHrgJP6+OhIVd9Ke0qSi7BVJD12
pIt2oXR+4aIElTrNDi5RLW5Gc1Vah19Y3OHpD4oqk6I0DRPEc+b6X65axMWB+CiW
Nb6d8v8MSMmzxdfTulHiSlJHNcz9aZcrUL14zxgO6muei5bBMDntCRyJHFNr7BL2
d7gw29srTWcploXJI6xgAvDrGW7vhIdt0MBGomuXZgSY2wT625Q7c3QdKOasVwxQ
qDR5YnYIhkRbYlf8Sgu6b0PSgSi4aZYkp+mPwHmoanQpd8TGX+6kLYsxnodzvyRk
dQ353EuyEMSMbn6t70SJooxvVKx2KQ0ISlJ1O8Bcjg1Owc6t6VfmG/MfTAneZWnB
Wkb2NYwENHiRutfL6pSrtCmCiGc4QEfa+WGDiQIcBBABCgAGBQJapaweAAoJEKJt
bZ/giO1YIAcP/ieBbsmAtCCXjIW57gExNziWk6SVwUX1XuROyytRHC71afT4U8Uq
YQpDrOYJCTX8Q6vH8U5P2WST01pTEpM7YuMmgikaqjcuxSR7ProFel/TS1apViXf
GtkZ+aVM3ttaonNnzlvGISLt/7CCSb+Uci5QUzpJwWA4XydyU5VEN6VVnqGyeAna
FIoA74+6GrUYhK5/Ne3OJsDo1jgI8Dj2uP7dnr2Fa+s7w+FRE5bDvY+MkwTkNQxr
edZ3SPhrh6xBTm4v+HpETXD0bEUBz9pCwk0p57iehYwFEJr5H6fJTpZB03XUy5WY
ur69xYtR+Z2ND8JxbTh8PakIZZ7ul1KyHiVAzkiwZ98c+DYzfLksVg9cztSXa7a9
3noSLHaHD+b0Ks4kgY7CsNdqnyBuh7N6Q2lrHdqjCNBqgRpUmgP0sV8Xtv1q/S/+
EbekL/lyiLtH2mMvqQGTig8H2FzQ8Y86/sAfP3x9++dUxyL1qHLr+uN4y78kaEFR
Sq4CU/LvuA7ZmEyahe3opnYaQt5ceQuZp3I69QpyTti4t+x9ZI7m3wK/QLXZW2ik
YY9/v/dVauLpj3F58kOrWfVYFbpdKxdnr2GHe/n51COLdcHt6iUqqQzyrTauWMXP
y82r/fRLFBZFo0szzJpH8u/SAOvOMRynmHgAFXzsEbwiiGgR1Nge3RiLiQIzBBMB
CAAdFiEExCr/fGGz5EoUVM01V692LbM1MyIFAlqheVEACgkQV692LbM1MyK1QA/9
Gv3fFcdENQ23/OzuBOO5ZMhZOUOApxgbC2m56OBllK8czCRcI6e8SVM5jHbP/m47
ugaAum5AAsrA8aCyF+XZRvuqPuFf4zsSmJzrXGdw7p7qkJ+ItpJOmsn/oKna8lGv
irUJ1rKkrOUOio7PegvLHgGqU65tShdhVcjv8jx0A1bwb/ZE5rDhAddW5rhrXAh8
aDS4T/3r0OZkZNHnFa0b20VGfWbFuevpaYJbnw4Ngj871mmG75ASv7xUxtb/l7Sc
RD5T5sBJi9ffsbUA6EjajYosQUHDqOA48vhmPrm9WOZ46OJfLWBUXJ4YKoFZLePZ
zdZXX9nmNdFPC2YrE6Kavu7S4TnWT1kUUYv+NErEZYELmUNU8XW5EBamKrtPzBGw
SzCHGDmwnPwBG1vCdJR4GS//1xmFVg/gqtOTclhF3OdWx9kQ/3pjBacdCG1ovz9H
/5JANZ3cbuR0IumwRHyoOC9yYeKreMYZ/A9Kh21UxfScyENCrHcmSHvGosSCNLls
rmBvOAABgR9ci2UjxCGsY7am4k/FwkQ4WGoLC11o4tXifxFJOTPmJmwaq8H8BriH
Ky74hxbuI8G+r8XvbOihMuQJAPzYuuvqGeluEHSjbRg6XQnjoATnNx41zPlhgsjK
O8lWMp0k7KVFpcjm/ht/HTdrmtgObQOdJIWCtpb3c32JAjMEEwEKAB0WIQTtm996
1qVeIy6EUkJX/5vbzDAQCQUCWqA8/gAKCRBX/5vbzDAQCQnxD/4rBzysVWRfr9BH
8+4pCv3/D/Vdnqsp6g899QFx0esYdEEwZbgcfmxAdVjVXy3kaAXn31Oiv0kZXz1I
J1JyA2eIsWt9Glku7jTa3tU6NB4JYVxpKFx44lLyzzbYVCUxzn+favb8zN7b1sN7
VRlL91kfRiw4RnfWBP7Hvd0s1x8mc8dzikYd89xp7PksPwxispEKWMSyDIMMqLB4
ykSGgv0554hdzPDKOPcTtL1BbW9UqQHLqAk3Qdot4Rbkz5fIXo87IqwtBi8VbQUg
RGPyrFEr8p7upt9NCfZ5hIWmOY1NQTrYerUfCv6g2aep4m+kbb5zdfipZ3RJs8BN
yF+c7qb77Nh1s3PawesnQmXnwIh00HQvQbbrHMCELkV+ZT+FtKaN/vxs2kwSagAO
P+wfkEPvosNVpnU9JpNFjTf2iesnMFn40XJksF+Ua+GuZaLxdoXWIq+CA0raoZMP
Ek1SrOYHPuMgC1LDpd6KW5RavYxCddNQF1opbbC4dEPavJmX+qU6i4HhWeteO9za
D99GhZ980pE4hJOjaCnpQhA6ttlTnEVUAJbfPcfpLE49wjBJK5CUSaM8KJ5M6sGs
PIGQXXXf3D42S1H90UFpWHP7teEWMjUhBnpg4gC3BpSUOoishALGRPS20RudASqs
V7/FOgYPGe0pyvPHcMsVwhbsf1BWPLQxa2V5YmFzZS5pby9ncmVnX3NhbmRlcnMg
PGdyZWdfc2FuZGVyc0BrZXliYXNlLmlvPokCNwQTAQoAIQIbAwIeAQIXgAUCV0cV
rgULCQgHAwUVCgkICwUWAgMBAAAKCRBr4s7RSpkXvOwCD/4u9fywuVVRMsbs5qJZ
AjM86PnPQ6cfPvGaKfN6/ncB/NRBcRJ1NyTRQqHfIefSPllS+sjbfgy2vSPWIaIf
EEaA6mMgCsjl+8Isrsjt56mmspo4GMfC6Bvx0l6yOe0Spuomy28vVHoxo15lNuIq
o7yia/1RltI35Gs9LAjrhuIeZx3G2tckgFjfcEdgnQ4R427HnYH0puOatDyKGnYR
UDpTisTvlTUgNWKiZM2UQSWgvAlIa085lj6Ep5LvInezq0+VGB8pDpeHkfvlCOOD
32H3BBfzMsDWBb73W7uR0JHIg8c8IqDG22NDPEwU1pQ6xnKIXkoAEnTC0iouSq5O
BM65PMhapDbupaFvsnMyeoZIhiTg0lrzRYos8lPE5v4yEhoeJrl5wNaH4akx7fLP
X7eJl3BYUDqsxngc/3agPrmiq56hxfTcERg4obeVvuUUP/JfUU7gp78d1y7MN5LG
/xjYAb5vAia28AjYaeusrQngdiPkqAhDaB79VYsl3SbisNo7mGaQGdEc+v8zUlwi
3JrM75H5ucXhg5BqT/oMOv6KQG24RHVEqlZMi5oIB/lzdUDgrZxh8x0aJT5TdqYg
mMleb+VOrW5SXDxdo/osygSIc2xnvmR5RhKbyvhVbdtsFLN7F98OG2/ucz6pn3QW
9VZCJnhHOU5iN6z2GyqJoVczxIkBHAQQAQgABgUCVfsDBwAKCRB60KkcQL0AkUyo
B/40dpnbngqnCsXAl3WyDQYWyK07v9xTp8Zb64J2MSWfFqg3S+AEgDcArxHdeDFf
vvR3a6jss3D9btv6KwsNsVsWXFfQE4f2TRtG1IDTdjkggIcmKyF1MsxzFRbVPsZ7
4P1M/mBiZnyNMkV4WM2s8ITf2BgXq4M78Q+kZWZgUOO0cpnRpBuRQ0B9Pyvh8R6/
KCRddEJTGtWl1n/IjrPHU2Ak3gBzMU0Gsdzom/s+eVU6+xPGC5iPsSHUwASXFWBN
dfBYxKudLoViQuWr+Kw4gR+kvH4Yqeo75EeJyhXGzFf8pQ5Y+G2/RPuILpHbe22H
JkBuSeEVdvck3FP1NeLKigSEiQEcBBMBCAAGBQJV/ZdyAAoJEE90TaH18VOBeJ4H
/RlJN0F/9A6ssphxhG0Jn96PDclYNwEc5folg+STXQqW0w/W863tpERb4mjdlnYy
9orrTVTQ3jZPVTypGog51msNsGR2hVuJTdIg6SEza06QRqab6eiOV6WBk+eZZnQW
ly/6bS1Z5BudTZABDcHCvuctcWwFl7bdJRpyR7VQlO0NSEd/fa2WT937OHNiLSXk
yb1CF7uzfSOfODVo+d7E2NwUBq6YoGS62OJtsTJ5/KR3TdVIsVQxkGbdKxG32Lpj
ZB61LPu5V0orvxiVbhVVy3xK8413lqqgZ8KRLVyu1nJiSfJ4sU6fYRNmQzPMdNQ1
EMvr4XU3tj7+sqVoCRddN0yJBBwEEAECAAYFAlX8lpIACgkQvQKUJCH0iJ9iHyAA
gNGNLftHh+6+EBjsGgi+nqcfQiXqk/hZ0D78L7uB33JgtEFbrJ+xmAiqXBATteRV
f0sRQa6UQHY3hJEUz7uzmsZR6Lp52Jpy4A9N6fr4EnvTVPuuuPuA1pjGY4fVLepO
frgfraqLBKVkNk3sTULfyTcnvZuZqWVsKMTaWqI9QYlI8IK/cwuBFjPJsB9cnv2T
FmlTisQzq2t9BA19IqN3K6DG4UNAs80jbIbqPeqEbbuv66AKYf9+v4fSdgKL30YF
/4VntapIhHCwQ2uCFCIyn5TQjhN45yXTQuBJ/mNcdc9cIAGhhQB9+5UJEtf6Ru7j
rvIvR0WTcNXXuebMiwoj/WmB/ig7ywN/Yh79q0md+EipCLxnHAIKwbLGDzOsq+lv
yaCZ64PVLvbVNL8ArYhse55u7jCEMyoo5tGDraW0Io+QGPzZGXHz9flSOoRtKQje
n8yEdnupFK8lg0uc7+U13ABkWT0/4m6nLrNPJiCVrmyfHkYyGwnj59r+zor2ejJs
Y25F+gUczfTb1qqCjOZ43Aq1eugv0PYJq6pjSU8vJohr1gkLFc8NWt6KTsiGkrAF
lDPsr2NQeJ4O3J+LStdG/q6jgACEzaoSljURZSUwlozacksYG8yLhUUDX3dl724R
CQyerKknoTzBmZNfIB+GxHvP6bofbJgY0JNM6Pk36FRfTzmnklodMFwNRRlMFv+W
j5UCouR9cPqdjzv8poVN5vRQKD/G+x8sGczcurR2z1rbl7EEu0SzdxfAI0s+xaOY
7Ug7lRg+zlmGweqLL/P0upG/f2v8ofvVUgRl6KdQ5IMpnmMbYD3X4qElRwR78f0w
QAXQHWgWlfvjU7cKEqMtAwjxmq2cGwGafPSqqOtzMWcw/wvpOf7olJcBTugg45UJ
W6NZhahNpUehH8ybxpynWigziu/NZ83cpuYqT8sGc0aL8ADJ73NPYIyuqhnJybeh
5ov4ZlV6iVvTGmAz8WJbCt40pekEOjvTPoR0bQmpSaW42qfSDUMj8o/7MAm6dr67
913HJIldFPQgkbzr79vz5OS03SupRwf8D4Kl2JExDajvPtrmQNSknozcqZoDflQr
PbUZh8YQV3DlBak9UYJmiaEPtxW9OpBYQLXVVsvsaCcP+xe3OVZ/pWQxCQgZhbhd
Iy3TaFqa7C3YxCcY23zFg33QKuwV0ejb+mdh7Z+o6RyDCVI/BsTvC8ur1R+SpqVF
PGbparu4EhAvLps9r1cmLSwrrWLpg9HfdrYqW5gNxhCqI6oNj92BjHMlFUgENRmK
RkVnP5VfOFI5G0zxHmWoAEjK5WiYTJ/IMwOHkAsCqv4HgMjD6XqwXuBSQncOpJdj
fZNCJbltbmT0/QNtPSxm+4hGBBIRCAAGBQJV/cOUAAoJEKyFk2KwQTv6AYoAoJwy
CjY8kJ/KGevLqtU9Ji2tIxPVAKCw23Jk9Ir6q5FtPRey9nXjqlVOKIkCHAQQAQgA
BgUCVgqB7wAKCRCxpw5Pjc0DZsG0D/97Li3BNKNEwkEOsB9cIotBZkyL0iEUdtMQ
2aPQeBs09rrm0+zRroMDhYopEpU7krtQxGhQKWeougFxthi0iXgleWjCl1S+ZDu8
0vEYjogrWqF0xGSVQ+fIxQ5kJaJ+4LzGpwrA9+Jo9VQO50kTHGsTpfI3AvYqdMvf
MgGIafr/IkuuO2y7yFS1t3iZO5torqybaLaKRAqzxSi5cjXJOs1e1DNkc/+PFSfT
1VtBBroz00e3ip8D2TEEo1Chros1Xfg24lekWnVl/XS4tqjetmVs+4wkMjCR7KAa
m2XvfaLgQkZgw6yeB4lRBndlF7oOedYc6wuvzJILw+o0VZTQkLOk9c9rdgtx94Tz
bSHx/otzTObDncIvUhH3fHblRIJ/N7c1vu8rnZB7QpFAYnHX2cJHNBt4s6cjiPqW
zj3CEOwBEmFQOArwcqtnmI+3VEyU7wfnVyPTjJwhSPa6bB/sZySNJeVcZwh0HbIB
BXZYvH+NxsBs+tUZAt8LCAKSAC299CuEcMpVKRCHPN8Si9noWccsAlR6v6geCUh2
M3pxH1xu3Cm2b2kLlKXMhb1zsCUQHEDn5YR6E1dCkeitH03ZlqFE9OeOztevv3vJ
3W4ZUm9WVQCUI9eBQZ4e7XJaHKvd4FPcwWBeHJV5Iz+DQk8TbUttJU7MtyW5fJCN
Fow6Ybwg9IkCHAQQAQoABgUCV0XbegAKCRDFJCoas5NlF1xcD/9XeOZsy1ty+Ztd
ZORTIEoGoH3lklC+hQcw85m6M9jb5MG/5YejBRlnWce/JcdG0vqj7gV/6jqSEd/n
Po9cBdlel2g4+kHESQ6OYe1Nu4g5f5F0XRnPs+4X2WABzbwXAQFqC5X9tm7kwLru
CaVtvz4vll0b7CollYXMzVWCwaqDD00xRkvdFVx5oEtDh76J1h26ZH/gJTTX9Ilt
YIeOzjs3YVdDEXPNRyoDMn+Fu0B39GLbvREXsBDi9zkrcFBjPK9n8j+vf6YenmJv
NUnYsAogGVxkBHxywGK5A++RwZsnVPMcDQtjuCSrpIAoaQjFH6mHI5zAFx0DflTU
GJrRuwH1+aTNrICq1aDAp7OAQvfbog/T79b5a876XTJjynOaePbChdZ4/2lysYVK
Rietecw8pQyi6kiAozE7aYSEUW7ouSQCfeTXAw+NBhkz935LyM0RcaFO4ZY+h/wS
Ds6E99fIEkVwgShj6otsFC+csE6fdfk6B9ptySGH8TXoWAUKzulbEwSOdzv4YVkL
/sLZpIVXNKcltj5cWzFRY81jmD8sdPCTAdhoSbd5JGe9VT05XARsPULyja+kddJO
MSmfXgYYS+LOEeVqvHo7+ZJFOx1E4l2TKJNahJQjxycWOQE1epzq36+oNFqhGa1/
+er3KU5NgK82wiovXGolZXsXKxxvK4kCHAQSAQoABgUCVz+OtAAKCRDAwHYTL/p2
lZGlD/wNJznBCjWJfrTJrnABpFLaxleudPXKWexh4JVGYTy+eUz6He+FhVEXViW2
DVHXRD4KwZb2RxWKF38d66e1BZDUWM2Nq7D1/rB2LQ/IuRPE9vfuROK8l7AKbD+j
WnekBQ/5IHufnZgaTWILVM/+/kG3mh9xoUQopV/WyAOJIZ+SUxkFG7WaPWBBc5+Q
5nm9xIqwqjBC/0TVPy6IzPW6pXfhC8x9yQQXle2HNnkPbsE1xs5dfqi0OAihxIKS
qG8CjlgVje/nrRMkQ8UG1zJh8dHr4wkMbn6z732ea7hVEqZiNTHV4Y9i8xerl5re
wlZDwW5C5BNRhKinO0LXGgEzeewHeu3QACWoBjqkWvP8kHNWPw/pUgtulcHnFj7q
O+EwQbPr7sB9hCAnwu/czJfrasmAL/04JCNCCD0hHvQkNe/lJ/BgzCiTYWxKMccF
+cVXAJc1PuEOXQ94A7NytBkSmx2miNZr4AEosN1Ujggb4thlh2yxdbkv/Xx7XAft
Nm0/M0YhOj+96T40HTVNa1KNyWCADv8fAeVrQQVYTl1C/Cxseh4qMw6WEbT3utoP
7RuvHF0JW6zOnkM9bYMjC7fg5456eRqxwD7BEHw3jrKNvKp1pnvbJNhEoFemgvYM
nPAgqtcIZa9Go0JmUkInWBN7muvLBAv6NljDZOQGBto73V+LaIkCNAQTAQoAHgUC
VafziwIbAwMLCQcDFQoIAh4BAheAAxYCAQIZAQAKCRBr4s7RSpkXvBT1D/oDqkcJ
cBlSgvUUqiZK3T8OAmT2qfyKlb7RKcsp9IUL/ttssDXl7YQq1fk42DxToFi9gfjq
p6ILVQhBWCJDn0U1uoo0HyhAS3eAGqDwOp77czg1RDDd2Irpe6H84p6rF1oJwY1M
/JNqXuQNiK1jCbqHeFm780YK+1mbQkL+uuU1cTqmB4/NKuCV1w1ymK54QVnL2mKN
fE3qmlbzmOV+Xnyy0i8usIfr8hmGSlJWDehgdW3NOm1AiJVQ+Ey9eGDDU+GAGVYh
4qAk8Z1CzBYNM/3li5UI2xmfwzPkR3FlWBnsaONlLMz99nNVlYonw7bcSyHsuR/Y
G0Sz16xz21qtleKweS4xGhI3PuSbykVqHcYx4R/tmvoHtCV5wCjum/QtSF1FmqYj
ZU2rYBbf07ouTTa3j7Ob+kaJmf3rc/1mdOBv+ST96qu5IokZ6uaDAXYq3L0lqdZY
BuO77T/ieo1lVxR1cd5bk7ugBatW1ZT/iMH6TJvplklK6qtWJd36KHnrAxldzHFM
K5xEMHyzfFa1vOTigQC7/5o+1tKQyoEd8r4e4MRoCh+1478ONHyY9VfJ430ezeL2
SIQFzbwSpmZ+uq8BH7foy4oy3QgoAMvHE2Cjsf/GBVIjBTRv7FYDLue26gTg0xPl
LUyHUs1E9Vhe91C026e01d+g+sv6qViDGUBm1okBHAQQAQIABgUCV/BDSQAKCRB/
qxFCZ+T6BNNcB/9q3thQQgkGc1f0rTq+3WYqt7lP3TA6rzE9XmJHcKnNdo2quI9n
5SFk2HAO6eOLzltkM4KtDyNhpR0wCHJst8HpU1eI5EaxNrgE7J5MoLRTwzA+2fkz
aGJUCQREDT9SGlaEWdP9hCxpflP9po7Yk0U5gDtaViQbOxX9RCyv8HTb1ZT9y7nz
+ojTo2ztnibiOw5g18hNYuPg6aWyN8+GeRZbGlp3X9ueU1jp+HEvyEQypZh9MdkC
5LwzQ/BBw8VgfOZR2pkDep+d+X0seB0WAT5bN+O2OMXwzNkrHhHtgNNdLGlR9o33
pRtZdeYWKgSBsU4ZH7KBx8v02TGToAT4zDOpiQIcBBABCAAGBQJZlzKWAAoJEBdW
VzLgjl5BY9sP/2P+LAASZppQzx2UgzaOv4FyHqcNfAoDmGJYSb+hUfSxk3h1ppoM
fPcprXFeTHnB33UWAvLvZ9GEQuBx3zJJL8EA78nUEH5GNSc63fA98wbegkaHhSZL
3bFo9k2SOEZJjGyjYpzh/PKy9f0DkgC9fEpjCdqL/pz+8BI5d7EUXl3eWmT0MoSl
1HILpu5W5/UjmGB4VP2SmOuqTn7paSlCXPnACJz7Ji0TbHOq+xjvbwGSgU2bTEFq
5AtbPls1BNfeAtGfsa4aWTTv1fPgeW/QICERwyykoeYgPf3xp2kwVv1IRSq2evFU
WLAWAOYdtUL1p2CFReJjvDez4TZBZyL6vx+J7YlRFq6d4brk0zrxVianu3kIFDBx
3OfZuoaDCmVNHRaX1FIvspcpmh8O5+5/BOMcjdHrvLbSVAlzpLSh4Y3NsSvyuNYd
/DpDMz2VNnSj3zsWn06elLfxl99D5/MP9jjqq+m7N0zBSrTfNbJoFqFz6Jb1/IBc
5nCfuFUSrFiqUgjOGMjenvv6uwWsXZkpWW+W86j8rcGrQvnPIaTJjbpXLvB9sNyQ
p/9BAFFAi1HuhIBrvl17NhqLXC+GO/MnfAuvyA6Dupm8B96jiMtzi0foKgtfX491
aL/5glcqR2d2H91FdOdsfTGRK7f1wRMNUtZk+cz+dAhv4Hn39ADQn8M1iQIzBBAB
CAAdFiEENfStpiPrn+OjvH72e6A1yluQFxMFAlqgNX8ACgkQe6A1yluQFxMBaA/6
Ag1Tzl/609/k49TMSKuU1xfi7XtoVbk3QBlvEHUFgFv9NVO067YtrUE5zLBHJ8pr
1DMqSt0ahIZg7gT0Z7H6yXX2wzUQ7OSgqU3R7c40WTOS5tCm4tK/Ybge037OBydM
jtu9dOs6pquhlB7XtaM5cKMOs/M8DueBSpAMnkC6jhmUa+xwNtEFYn+mrs9O1I9w
aD/ML+1/jMC6GbWQCRI7rfmLcV56YXQdce/IZi3NVp9xaMOLv5FhLXPZ2VrgPEJt
xWkZ8V4UGi32gjNpY8mXyeKhMD8/6GjcsBnOx/QBqBI813Yr/82X+hRBGJfiRBIE
UFVqpRWl+Y0Z+vnkanR8jduZTJPHlLryMs1oV9Nla6AAeDpNblZ0a7PwSkw7CyTd
JUhbID3Dan6uvCG7KeNKbYcPKASAvu60PsPjdpvOEHqEL2MLzmGP5jkUHpupJR+I
i+Pmg2e4MDhTIIDIdgezTDW3fmxnSpDZi/G1PMyB1I5b0PKuJAryBAW5Xk59+KGt
/OcltpVq3zRqzUN5kZOWHwC6BLwLcun86vmD3AVk84elxalBTk9RfH9JxRV3FAfE
F9WDi/L8WQh0aGxuclpUNPBbngDGQkTl3ejL3I3fEkee4kfPc/+h7oqT5migTt/X
VCmWe5woMygyPUQ6OV2DHWidJOnEQPXTq6lyUOuAGViJAjkEEAEIACMWIQRgaFsw
qhgzq8lGLChf4I0aOmH+zQUCWqci1AWDB4dxAAAKCRBf4I0aOmH+zZAmEAClWZtv
k4oi2HHbSPhZeHN/K96ly7ctZjzlM7eANNC8pjxprqRbbLWKiGAIQ4iRkEMl5ZTp
80KermjErxAVd+hCaOOCygyzlcjD/zG8pakX+wBAguON7/TbVDTkCCgQ9EO840XR
DcWBZprFiApmAp9rNc5FT0rvCF6aFV2rJBYBP0U/eKxJsVlAVH9OaphF5vTlN2Mp
uFcnvnncNL3FGITjG6viKH26wrVY9jSIINn1scXVjcShyISud9u+85h1nzTODmmW
0nMKevdKO0arT5LhMlGilFI05ngVteKbLDWJF+DKQ7STpAXr6ilAoPq7tqrL6Rzb
8Jm5epTWioe+QecsaWY9lZFtVEU40IkoErpluk49jliMiPLNgaX8jjK6LKNUHG9Z
VP/h/0VG57EfHw7ctuuGiTkiJe8881cn18zIH3zsou7HxsyyjMnDcRAL5g02bIsn
uBbrLEm4n6iRTTBXBoCyNAUQ2t4Pwl2pGWfues07qV7QFz0hOC5rzn8y4kMFBds/
841pm0nmLuNuCmbg0CBAQ6D0QXLrBYKx3LtuAQTThfdNzLFGqpV3yOZJtD4lSbq/
bwbL7kYcX4ue4MFCuP3/7HyafLSR4Iml1u69wB/8APYE6vnxkQ+f64uCIrQSj/0y
Kne4uswTq7H3tbSaRPmaQj27YiF7l+rUISuJ24kCMwQQAQgAHRYhBK+Rcxi4xC0R
JyFiXRV+/Ky8ZIQiBQJapzUPAAoJEBV+/Ky8ZIQim+IP/i+f52eaMv9+rkevMJqB
dD/XhAkRgPBRN31FAW1jo2LfbzgKIof54UplDi+Ci4njCQh+DOSZLKq9RTFtkV55
uy1zoKaX7sBtzn9HOUXvHQyndE1uXP2ZOgJ2bMMd1ahDXGHiyEof0+LLLEsdQFPk
R7ro+4zQdCFQGT7eZ5tITzOnt9KXVfPBuD7kqRTzqGqwr6FA1eSljGkUppRb10F9
pcwBGNKiMoMYXacmTl9VSazWK6TmrsPBELTiQwVEayER8S76yWNbJxE3jOB3vbU1
Jk1HpWVOQ7MObpaYiASQcXS/5w9FTVZMhm9SMhgAP6W+Hn7tUSd1UVh4XwUkuWqB
mZ9GMr8F9Z4ZBwPibyxnBA4XdtZ0s/HWUReDoNa3+vJpc4oViA04/TvjLR+tfUkh
wcT0pGzdmHSelRiCXMDP0raNx66vBcfbG2UXKTtGq+O0vCKxfEQt8awK6t9tX+bz
Hi/qNopCcOuGJkT74UaXKw9pqH9GhFDcPOzfaUJTypeoAvI40xQ/8+6SwByaeoHK
n5EQtPodx6PSNK38CNvzLlwCMRIDZTkCpD5W0JjH2Yzth5eN0zzbFrziWjukcAl1
7i7s78CkHbrBAuCI5KE+5dIcRLmHazvUItDQjzA0QG2R3iJ13TsuWpHb82+3BfPB
9Vy0nQa23r+Kgrsxhj1MC3o0iQIcBBABAgAGBQJapvH1AAoJENMAEW4ch1o9Wj0P
/A3Ag+lA9AAm4JlCPgswHUgJCLfhPNG0V9G5mbNQGaeHGzVwMoLW9s2iNhthHbMe
VhVoYsWcuZxNm3PRPvy1vlAoaietRqvqZI0VY6Df/SEhhQkWLIV8Zst0KrdQg+rp
3RxsDdyyqoEQK2+M1ZtP8Hdm/kXSpgtxpzhUpCy2P9WgrryFPmLPfLzeA83q02Ps
V9kUdYX8DLWSW2MOIP3gc4TXu9kYpE35aOlAResDF0X0kXhivfvoi7VLaHJj3Y4p
tsKpC7NKiqmLb648Z9Hg5tAUsJhCGmP9yeV5ELxg7pfcFJYzwP45XEhIxZ4UyoZ+
8fL9lRRy5KdOLpWF24VbnihpOYgfVuiyZgSLKCHmME+KOrgBFT88sOm58fkQwE6D
tdVmZlETNb2jQXVuf2iqblykHr5xiHeZ5nQvqU3Pihz/EHuskVQTEUexfv2bGmhu
RsHkO2yK8fvyJyHeyX3iwdNRNgXcCOixdEfzCscFUclINpe8PegsIDckXCquP0fd
sMwIjKhE/yjxGe0Kq3Hts+X20Cdx91MJEFHX5GABHbhlX1LGO8I7b4H/YUpc7Bq6
D7fXId7NvEFz4H/TzaGu7fCoaLLZgmg33gO0VYWuyhPBRPDaVj7Yaxzs+xvFH+yj
FFh1/iGkyiAFtce4tq+iSfjwLM1kfMsgSodOzmTzXGdxiQIcBBABCgAGBQJapawe
AAoJEKJtbZ/giO1Y3lgQALDBiFGkgt+Jp8CGDcItj4p4MeHv5c2illerOo4MKiyP
1gx79BwTLWoLc1CZiJB9C2ZaKpbpq7yLp61rHM3Bp/F7+UArhJPvYMZ1y1SAK9C3
Abbg1dY4oirhMXfbidUhmETG3hA60rNQeE6a9yGKUdTAZCc6B9eIec1NoOSSRvRH
GvvwMo3Zps5k+ChXNYFKN97etCZ2z7xl03SA/hWeK4N+w6GYPRKSYIToEoIuGz/o
qkMesALNSSm9D8KlYyK9kktSqgoPan+TQ7uyowsX0g48GYCsojUrzp7NvH/1Wprs
/j+blVEkqqNNbBdhumPfuX6LpBGJd6EJ9MmjMRWSnoR3y8xJbcOUOAS9+WCuyoUP
Q3XRlo+ze0nFBXMXwq8B4tDjW1t/nEHMR4VgF/yyuneQwguD7yte69v8Kb0EL2TX
TBaY7TDW4CmyhZNWFTwGw4j6dxu4xYa2MPF8/YnnSSKrHFcuNJClI62ulFo/Vi5/
GdHj85faOgz84b9ojGJGDqm8VekJvxpOKAFHTlp2HbyLMpF8ywS9YUA8+5sg2XjC
mE6d2Lsxq8j9gx0E1ZCRws+6GuNTl4Jyqxz6H6Vub9K4+Yrq9me0K0qDC26UxscP
uXTjbqamLkSMDdXSVk2sZryUyKKyE54h9f7aROD6VshK8Z6y/x2H7ybDps+A5yoj
iQIzBBMBCAAdFiEExCr/fGGz5EoUVM01V692LbM1MyIFAlqheVEACgkQV692LbM1
MyIk/g//RrWeNo/H2Sc7QY7V+h+jH2NsOKcIVLkCUSNTnFW4ySDG8BPmTOQJNwOE
W8MzwRmKiYE7CZFHK2ren+inoAkl7o8Y4/2nCGykQr3VPU7fyPYhDz3B3lM/aeWd
Mj3ofDk+jbd+MMaU3Pay36yazoNWOVRMv7zMyyDzd5tTtce4rE8OrhSysRMiX3nw
ieoftVw/5jTDYD9HU6ZG1rKx1naNGjfkzNX7iaC4WUS389tMYf2GrWOIMrc+/y31
7sAm3Y2WtjVi9EXmjuYmXcmbud6Ssny4bJrbnnAdNkYfxNUx+0EXKlIUJjNR04i2
s2SQ3scZcxxwQPj0T77iT7kctAC2/ziUDORGlN5XxMFqQmTNA6U0m/hxihD+Ljoo
8wUgYXJbSWhFkV84eiX7rusYz7opKAtHq7J5e+pehHdT0r2v4ppcP3sSEp6pHmjX
aTB9EVux2npgIvXPKTn4OUQHzfqv0M0scosTUqAgb80ML1MQnoG5WY6kz9Be1u/p
KRxgSEoqReBiNyYvlLMb9Ym863Bx9RCWLZ4dmQ6QiJnzv1VKrW9iYEUkRMehoCA4
dPYWyWBRWTy32+emMh7025CkrUm28a/32AOvKpYAGDhsXYHKFo0ncES/aNteZn47
wsIUAzdMVOWm9nfo0r24AEL8iTKaBAmVOH+nCZvqV4G/bV5ZgIeJAjMEEwEKAB0W
IQTtm9961qVeIy6EUkJX/5vbzDAQCQUCWqA8/wAKCRBX/5vbzDAQCSilEAC865k6
fgE80Ns2Fa3ul2rdMSUeYDYdnEoiQd078NdXTLq0oK+nqhXovAjHy+SI1ZMrr9uq
leOAU98sKqfNBD/xa8e1Njj91bLxEDeIegAvVqtctQqIOwNYGDM1Tfx6ZGEBttlQ
Y71YKCF11yAflUWWow5mWZwtEri4TStgJn08dbgdFVp98gPyNtz5gcFOiJgdXg56
x29jBABRWyqSOHCME36hKVm2frBox9hWLBc4MhL5sniY5oAYZ/TXfdycQ9YvkWvk
jEh18H5GpyXgPwFSvhKlXuNuJ/hCXfpOqnZHbothRi69U5iylsp2ZLRIPqcs66LP
7ZlRjQwMg+qzhtDRMVb571Zx4O+ZRXx8pLY+vPqv43x4xSMDnQMAgiGObC3SfIKM
DPG426s1J94K5LGM3wHiiq6s40fDCmR4dcEN9iRodE0wOkdPOIRHMYSghFd1cABc
u1u6NY7sd6BltahsYCnD+EAwZeVkaQLL8YyPu9scYWxAb6qdpTl0xwLg+LpVbUKi
s5hvVQe2MKzR215l5f7RFFZsAhrkEbfaVwxYi5IlDxA0E/f6jMdbD6ys8zSCIooK
wU4sUDSVeDhWH9rin7VbGosBkVPyq88eyN27nUeC7xPnW5eU4mU2+KI1K6LjTuTl
aSAjP1AcnPVifycx3LseHBG3qOnfYOpcTkDq2bQpR3JlZ29yeSBTYW5kZXJzIDxn
c2FuZGVyc0BibG9ja3N0cmVhbS5pbz6JAjcEEwECACECGwMCHgECF4AFAldHFa4F
CwkIBwMFFQoJCAsFFgIDAQAACgkQa+LO0UqZF7wjAw//QswGm5kJsIx6QIbxHmJq
j3rHc+J+na4R/Mog3vnP9ZsEBEPZc+N8ZQNcPx/4jh3Yjewtqlrq1kRwSJJ0GGsP
bY6zlRFBrXPK/5dvUaT34FN+QmCzbEstZV6f1Omnqbz7b2N7HK344zvAEuxeuGfP
P3hdj3ExnmcJHeOj7PmvAAza+FlEgId0hATUuC9/JPW+dTwi7NrQWXCFSyJMDiZM
Yqe/uxL03BxpshGpodeP535vyI2ztgJBBglb6EhaMP4eYiOWpHqTRmF952PX4/f/
L9A0T/MdUvCO5ZEZwdmx4tALF+LlKYcIDm+KCxNylR9MUhvVDUSKJ3QzDW0vXKl9
/2vebZGKFgAML8PQrZMPVTrEC70Xgbcwa7OBmxTMjbxxFjfoJ41Cd0YRpTEvPPAr
3kKsQI0M29UIyUyJsF9r+DuDvqa7EegehrhCpj56szNamDGD+txgqVk/6AsZfDCc
4GDmd+ckez6B4ecxgygNg3vODpKdOQE7ejFmzOG+eyh79y1+F2maB/AmPjP09IPa
g6xhdMXfAzpH+xfUL6yVqf672sYqeDu1wF+SWTd3+Qpx7eSeMp+tEKPNjP3M33YL
Xwy9m1tWvGDjWtPBzNM6FZV0wxH7x1wbozud9zsJXuzGrNdNqo/TLH+B1vIa+uRz
mrphBumItH+1y1oYSuupDnSJAhwEEAEIAAYFAlYKge8ACgkQsacOT43NA2bAXA/9
FRxt6+4DlWhL7VIPe+mFZUTwoqsqy110DNtzM7Wgy1OhPqVn6nND4grzzSA0xlHZ
8sYme4x1NVzs/uubboKhPSZsrELii6drTUa2rq//epyzwij1jcidKuOGsodvHkcL
wZB0MlXEKqB1JxVCYKLlFLsn7Dx35p+HDPou3ndmw+/1vhfZASjfJOwgKyXAxFLx
8t05jjp7VS1fPVKXKbmoQAFWA5NB5mBfZysB6MLVVO+btSkBSx+WoQeEIRbs6aTx
5WBWrdheftVsmms1+LjASaI11ItEy6H4DmuVRpF+uN6DLXhShJ9WGbBo/Ykrf7FT
SpSpkNydGnuW0ZFGQlbWs7IMIfl3mR+n3qi4KJg5oYyOqp/6Lulo/COacx7nIwSK
zOOF3V6iyTxTpZ03KyY/76zY/ybNbYD3W54yp2UQXXQxe//lRw9ydacOb/ZL1D4T
1hdmbuAp4UCz6bSRYiky++FuwsdU1CzOBUXbiQIUNMXfW3NHkMi7EwekaTngyJUP
Cgep56fhO8dOnxrKQKwN1LQTEDHn1W+o8qwx5JqJ5rPAWSK9VAk1/8lN+w7V4vyz
eednWIey7RisXE86wcTIhJ12fAyuiHFPSw5Wap0/TL1FrHVyH6hYEsx3llrbLEde
EMmrhvPcEqDik0pIRqBtG0AIE8gOAMpESKXWs67yTWCJAhwEEAEKAAYFAldF23oA
CgkQxSQqGrOTZRc2mA//SxhTGsBUqoxcQ9i4NtAuZPg3+dhAIFrOOf02niFGXpDL
lEmDhZ4J9nUU67u2Xx422x7WAj1xSauqdJziO3gMXxG8HTFYRNW9g4geN4SfsSVY
zpOk9Y62Oz4wjfwjufeduzpEAdCZRqFK7aPymv/1fyadbla2Ul2fBt+5WL7JUyiW
GZNjj/VL1s703x4IozTXzfzrxhd9f62oOE7yvTwLwUVa0tvWLs7GHLvWAvTkKff5
khmaw8WkmfQZldYMbVZXdpqm9IcjjIlTZhIrmaOLqcoNYvuCMu3+BLMEc6M/eMQn
y0MxXdeaDX2VJ05V2T5MIsuXQfhMIgMY75N96snx3ZlNDEXgjzl1rVaCi+qGZ0s8
uPtfC+fCgDsUzLIZcArNgqtaVSpzV4/i9XOmu4mz/eLpCCA4Ot39+FvW9nJctShE
VadH4oNPnzf9WUaHdCpb6TB4g5w8AtjP6znILjjX61CaljbwgO7puBfSAZI14Drv
iZJrxE128Ymflqbvg+Cvcxj1B9DGNCcbn/41QbADyjH+hodIb91LnEXK8tJoesJ2
sCPNQ8oYXo42bvRXtN9k8Xv6h2phReOywSs4eefjAU63vOmS5QBryjafsLhk5bRC
Lc2f5vWYzv/ESZkrF3ZHmJn7h7P0xLc7LtVlObzqRFNjLYr+96sCeDOGhb/RwYiJ
AhwEEgEKAAYFAlc/jsMACgkQwMB2Ey/6dpULEQ//QHGk0jxZhZ9/0WfLlk6STU5x
OGOujzkHHUTUtV+cXqj2dYGAqKhU0iTkQfn+iJUJbi6i+B4tKhndrd4KF2EW2S+o
PPvulTjSBUNBjAZbi4I7+HOAhBamqgIGUAPOHk7j7QyTvhy1OF9JZO1Bll46DsgJ
YISi2FGw+06s1R7me1JU6ZKkk+rTZoei1p9idGbfya0PMWtvcJPZFLtoQLqjLibM
sfE5sYPrjXCTHP9sxgFbWXIEgxjSM6OLWZPkp9OcnXEV1u+yJDrqYQ4PX83eNBqc
gQ9WrXJz4UE7UtiyPeicW2eY+Hk12ffnZUWcbKy4tFbynOvHYVqDRwJAzbw1+zqU
5a0hzERCfjSL/IqWMmTbe3LQHK+RuE9yBiVpLUmGX/srexDdTaV0CBPrKl+tDfsS
jUnaNDB12KDmPOfASNnlJxKY1cEf+xLmqgw7wesFhrjwJUBplUfKHOeQ8VUdeLGj
ZUp45+zrItiZT9bl5d1IhNZhpNHjbC0oUHQ7xxrU+r7UXtPcnqA18jG5uzgF2AlV
4eLixOF2PT1tLWyl62FEB8MgeKjcABUgInrenYpWG2yxkO0s5tAicl+zMQYdJ9Ap
PqEP3NrjaNlaXaI0qts4xbP3AjneVQMVA8i3sRhNlEGooAXxWr/IpGmCnHWPzvgf
wCfoLGrHOHMf8fHLd2CJAjgEEwECACIFAlYC3OoCGwMGCwkIBwMCBhUIAgkKCwQW
AgMBAh4BAheAAAoJEGviztFKmRe84SYP/3kk2hdP4mLxsRWy4DmKA0Gy6dL8LC2A
Z3Lkv8cvp/KDVqXwO7B+cYgQ9O/0oDqJ2dm5J3pYZgyY4wu8h9a8hc6FbBuOkRCP
u6Yl945QJK9tqErJ5ad/vto8hI80neWCtCRUCDrtkdqwi+glR9kR+8Z9H5snLExi
VIGTFyqWEVBxZ5fwSckJnziqlJrrnIN+2+/LVKw98E66cAcbGFoeb3WSEiH5tD0C
Jmqmd7L5YLohoT07LTfI2XSZJYx/apzxqVew6quwVZjOYX1pjzECsYrVWmorHhz/
IMRABR7JU8H6PoY9ffZh8cr0EKsZ0iKViCfgA+O96xfvvS619vDcSfcX2ncysBfr
Xh2X1fux1iZFpGqJ1mtVzV00c0fejRq+EcES4ppKh9soDajfB/qYSHrJzZk6hp8V
ovfrPx1rqCLndE6qdU17kNX6IvWnOMVPSrXLqy+Pr9xpbAKftXX7cZcoID3Ozm6d
Jr+0voKi9ylIQ+jiJ61wSK+8Zwem23GNdnkIcdXu9ape3fsFPkhknvSnKroAHG5e
sMv8K/4irxbIFlXmQuodHMURgssJFL9e72TXMZe8wUipZRfAt3D8dTe6dg514fnW
ma9UA4DmqfH588LfmbIg2yoc5L+Dfb/uhxS9yHlhziS5x3u80k1c2NuJFl3/jPpY
8psGMhIVOVKpiQEcBBABAgAGBQJX8ENJAAoJEH+rEUJn5PoEg5IH/3VJFS1NxMvF
CVMP7Pw8uGCVDmee7q5iLSY/DALtLLi4h76/vuFN5Fmn4NXcLjdDC0W2Q1NtbimX
RFgt5w3C0i+gbA+NJqZ0mnK54T33GuadZvF7lrThTf3XdLHN9LXrfpw3imCgoEMr
NgMSeWMg6hcQIYe0CJCXfAVdPaQF6UZ5nCeKDdXm4HA1q57fdNeFA8AbFHBSum67
uuYl4Vu1SskDszn8tEnNZ5fz1qwlzgZYaf1UHeB5kj0WxfeqdFJfHazGO6hwyRS7
wd7QAz0O++J1X5iBYtOC2qC+bwPhxDB2Uix29q8U9DLYgnLKIwXnhLua5eEG3RdF
ERw1/xI64AWJAhwEEAEIAAYFAlmXMpAACgkQF1ZXMuCOXkEPyhAAmUJwP3CTUSdW
4Ew2n7e04GpXnGO50eT6x+gjNHodB0mFfDHmuD9ObtCSer0AJcj3gu1lIfeWmWx1
HZzJm6pQcUkdYycZGmvTgiqF3rzpoGfphDT7DjwPisA48D4FfgnrB5ILiZ+rA7mF
kysVKq9vCmTv+yN2YoozwzOY1X0+1gI4N8FcFkBNH0a9ti+Np36u5l4VLEZdOMJm
7CsyMkF39C20AqUFPQGZuM2bE9eSPn+mALnf8hQWnsViXLqJ8JTY6uKUycgKv5AI
j6+nEy7mjwlAOgGkNZXY2UsAHuqUxEmWRHGxirYUaY8iSguNb2l849H/5yjlVoH/
2Y1PXVg1bizD7HcFqmldldSr2EbUV32hs+P/9kPmoTIOlXHIL3IGXiv3VXzeFuci
CpFnjlkfbocYNiXbo/R20TVvAOYesv79S9v8Wu1pEydo6jJGL2oG7RKIxIAtFfg3
L+T8DfWLkmLDa6aeDxCbyZNThjKtBYO7soFF402XuI0kgK4+Asm6RG3q6xcYRt2f
Atx/NAZ1oJ4c8ACK4Dq3f/a8TFaOjEai6hNIlYiNOvNxpsGYrN056s83Vk7pkve8
VVPGDoPk8+50FLIkvOj1HNoEgzYm3GVbbNe9ZBlL1SIhOzSE2N8J9d27jU9czFV3
QZvWpNQYYqRrpCW7H9eeEvjtrfCs7kiJAjMEEAEIAB0WIQQ19K2mI+uf46O8fvZ7
oDXKW5AXEwUCWqA1ewAKCRB7oDXKW5AXE9+YD/4sO7Yk4RYrdpCgYXBmcKZg5SBM
Hzo3l6jtg3LPhjS6dvqvyi3cYjhIWOaVpBPR5eA12b79WI9/HIdhrU8iiYN+MLHJ
GufpyN8mRIPmUY7Yb9FyeLb/17c8mYqm/1pS7qv2qqht8TslzsSM2SZcRVBrbDmc
VwMQWmZMnDyGwdHKS07VyUDRfL88OJoXQcUapTQ7w4c7DWXxp/vU/4SARqot9vcS
grbNpcQ4mwdD69ExsuJsRS8H44J/P4mDI8D6S45SjuLFjzJsE1D/lMeVJOwD9cGz
fc/LYBrxX8fSOpZ9ExUH56WVTXhvs3u4nFed6oaMX0yWxxEtLvoXULk8zb49VIo4
dujXvZ3dbl9mGqLPiay69ZHIJlkhiO+80//yqh8TrM2KdfnGvpRTpGB5FelyM6kS
1ZSKNW4z3Lw82YdpMuHptfSMSyyVUzXaWh2Nl+hI4/zgGBWuVtihAq+WswKDNxYp
zr1unt/L+xtdhXpb8IzIix0qosQixj4ozXQk8BmirFipULTF5+QgAT/XB2wYU9pI
/+6145zdxi7KVFj4zAiiXsJdDg21ZtjFgK5aeHjcjCDdMlf2W1SEMqCgUMhbDNND
YovXyalxt9hxCiRnhC6k31UE2Jw3F3MZCN24ZpBQFzApwBMPgJgsntDXbwH0oOXK
O/j9/VbNbVScJmIjZ4kBMwQQAQoAHRYhBA5MoSvha+aRVvVAyZhPEMx3Fp/SBQJa
pXi4AAoJEJhPEMx3Fp/Si/IIAK254P9VNb1/ce7A0qjWrO7Za5CucXiFNJFBswac
dTdxTvtDk8+RjoZK35FyPRzpXvX4x2t6dV4ZAK/LlMEg/UTHSMC3XmdjraNK25XM
LT/IsXxB4VzeXFcf0/3Qr7SgfCfGz2cCTlMOM8fLvyaHVCRCgvU9s9QiU1ZCn+Ab
qIs2qScePCPjlI54a5+39YC9CblOMkrgtBv3JdABIZ5UfBxO23gUYqzM6c2LkNx0
kzFd970Oni+ZWR1tPO9IY3Z9EVyZtfAKTsXJ13etTigJOp5HtfnVEgdrlhgk+wrI
dk5ZPFQxM1LgBwxhve4bf37Ah0gl9npwD41o8OZlu6LYtUqJAjkEEAEIACMWIQRg
aFswqhgzq8lGLChf4I0aOmH+zQUCWqci0wWDB4dxAAAKCRBf4I0aOmH+zeL2EACh
P4BLZ4W0RccQ/VwhM2MJEfa0rwwrm/lsKDkIG8VxHqWnmBHwukvo2Wpl5S7I/l4N
qy1wgmGXxANojVrJZo90eNiDDEbxYUkY5RuLr0edVqUxt+BTxSrNh7xf0jfM2eko
GUhlUAEUHoMtftV2eQEXhCBqp6y3ZYEJNGU7GByYs6KV8Hr1fkjlTSt4K6XOHd49
Svt8tTFf5ccugMQQeWcsg4S++/D8vOl8XkurCuM1ytBJcSDUoA6jghS7O3GqH0G5
dubjh63UfQ1xQEt2Sno+aQ2IKDGhmMyi+yXkw0GqD/nvfonZtcaCx6pE63L/Zaz3
BWVqRGus4EaqAXrFuPI5jDSJuSwDQe4jP80c9O1Jp4DojwdTJcopEnG3aQgFYois
aJON2FcPJAGQWxWYt9udKmdHM1rx3zho/CJEKWZ9UmND91Z5qFKf1lnFNWXv7tnY
WhWrCtk1hugijvIJ7xOBAVVdvydOXZfI5juep0VgflOtS9c0eMgPRVe0YFM14BQV
2SPW59y1R1gB5OfwG+0GQxSv2JsGTchf/x0KyFamYU8+NVzmAoebTQ8fvQ/Re5yy
KtK2WWFaohKoEUuWPfIxoNobt0Ju3iZrFDvDmmSBDopLnglMgSDfd4gN4eNfYlyS
bSuo2mcYeYvwWceC/eY7m7QfgmXHx51zd8W/jprIk4kCMwQQAQgAHRYhBK+Rcxi4
xC0RJyFiXRV+/Ky8ZIQiBQJapzUPAAoJEBV+/Ky8ZIQiGAIP/RZO5iL31M+uIsb7
/4j9JP8bcU5vEVA5nyEqjqIAblVppKDH+rFdE8d+cf7AIgeBGRcM3HA/WwEhWCOO
+cx9AF+9H1T+Q2Kh5kZjWLMQcJo5dbRqB4SyB043/r7k6tF24E+5/RR4xTYr0fft
Qs9B5m4LYl9g5Bsz7TQ1w7HwkvyFvgrtpe0hVLPiE9XWC07a26IpRywZRB2u13eg
SKKPmHuUcXa1m8MI1/Dr0P9IV0pEJer8v3XDaRLCBrh0ZqyDIGt0LxMNc+PMi9Dh
9x8tF/PQuTeMQ+FKM+FFEyvJMuOnmL6QvxU3yT9Q6JeIcE0oSA9YjEjAjKxA1tRQ
msbHmaOIVy9kZFEe/gel00ilkp26b6CA2k+Gs1UrZu91YeO5aV5pGRy1dQItFGnD
Y2sZJmLEdrHhMcK/4v6Bbr/D837uF6pBB5WawvnmasNp8ssNLJBkpIspJoW6aaKp
ySm0PN339ymzmZr96yZG2s6xt4TwGXv+8rqGCkYE6xZMx/H8OmGbn8tm0qKq3rcH
xVVrEh5Gg7BoRt4k1TxZpt363DZsnRRm1Ww3g4q9i9w9wOUQrmJNxlpqzJPFCDDH
PCJXDTvlU7lNIYLWoCWgX4U0zZbZqQHbsAXi4SaVmBO1n9bkGtPZPLnr01+uh+LZ
tyY4CcIqfcfkmObH9e+F+w1UY2OpiQIcBBABAgAGBQJapvH1AAoJENMAEW4ch1o9
6/8QAKnJYwKgfb0zN2N+IZEC5O9os/zdm6Z00SWSL5Fj6ALTp6Z2WWT8oatvpglQ
4qTj102DKzZN33Kksaxj9WejxEQDDAA6GsI6eZj6Iv6/u2u16SRtu/hoXwbguaYF
9nlz5LgiB+heBGErRlvE1FA7G7m0MbXtgfWZxRYmKUIE7U2GuePukmkfcO1fr7TB
mQBtfWVUy3TZFuOHAsXawQVWubVQ4g2QqP/Qp73dfvpOGru36GgfMfi5srPsS9hy
up3dQUAlGRRhdyjEgJuvRNCCjFOd+vYYzhM/qUoZGefsDXEsRqlj/ptKub9R3ZQn
9MIpAebhRJoEOF+TMzCBiJi5i1xu30XAm0oRhz6KRgVfqSm8WRVC+oMw3Y9vQPgk
W82PyqbLmzI8pOXO1zXpm4TNubosZiBHhlEZ3YCiHdELfD2GjF0e+MjDFB9kGpRO
542CmcPp0j7QxJM0V/bakYo81n6vCXWBA6DD4/DHmqNZMjmlSYEpFgHc/a0dKA0q
sX6tTUAOg4HMpk61to6pnbogX1HGaec6zfrf7erxuCPz85iISZJ9ryg1rxpNFWZn
FggIos5JdJ2M9ohV1lbK1veqYXhPWYD4HwWMMBCAbj2CW8QpMHM9JO62xSwX9nC0
FS+4iHKYKvjh9tvH9Aq/wlmwzHY4S87Mk2ulMeRh/qbUwuLriQIcBBABCgAGBQJa
paweAAoJEKJtbZ/giO1Y0b0QANB3RCruv52m9HI64o1GNaASG2kBTtQAmpqPx4hj
TFY1WqgEJhF2j1NJ9kQIg+5VbvDOyilI3TVWL+ptGiha2S0iwn20JrHdmrXieLDv
IBsTeYg2egC06EGRsLcHOSahS5dpMNJf0J6Aggt2Ov7iIxKQojgjO3yLhzBlhlMd
Hl9wONcwyapJFnHXhw6ZagD6u+pvCbLWqKAOqEFN9MPpLuouP6R9+hJqgeRd6aDd
WLGV/9Uic0yedQWUUtLT7Y++a+2pYQcDME23f7+Z9aAe3udMwMwz9UPzcf7fKCae
8uTXRtInjl4i+7eAkfPL5hgm1EJXdwh8HkeytyTjw1bSMHAgG637UtuoXHPWLen1
tsllEz6s3afXqtBgm8Kq8Q0b7QxJfoV2jA3Yc1uipZcSaBfUpqMfjm7oUbAvQxuS
GFw07mW6VM4vYtON2LubYiJi6U5ZF1XOepNWk5vaOb10X6trIQ2qL+qBaSSk/RGJ
N9oLYF8Q2aDPeGcZpwUhHHAv1cr9I74ZunosllzO0Pj4Jvbsim09YgJM5Ck+STow
RJEVa89QL5r/RmPfag6XAXWYuodzYAoFlRmq90XvKuJ3YfBdH/wgEJ6NfCNWQl4W
Vp41+UMhDB4TQGjdxaDaTIR5d4A6W7MNoZkVE+3TGSLkUUj5gIoy+TNWu9z0ZPFv
hYAsiQIzBBMBCAAdFiEExCr/fGGz5EoUVM01V692LbM1MyIFAlqheVEACgkQV692
LbM1MyJ5nw//Q6JqB8n3b/I70635n1B7zmGA2u8rKjZQ2lZi4g0t49taWlMKjVZy
5oa64UQWWu0Nn3PcLZVQhPsJe2RNpEU0GYC7mUrazvnotdQe0JA/K0/8WLgXpcuB
B/8jojkpFMKcw0LQv1ebvy3vd2iKiGprjbHbdJiqtDFv1qthLcwPNsAk32k1G1WP
69XzkKL8ULrcLP40uDHqTSu165uzk5LOX0V31U2AiY2EFH1uqMSYrrOjgAKGJ4bn
Z+P0PvUFVzQiL3vuwxwWw3Vsg34uJGvprOpsa/r49YObLdCgvER1OyLIuFAWNfBJ
Dc9jKJa9Ze4feuJMSxCPsv53f93506CQ20bGQ4fXOhq5/dZNMnygAHPkrFaSBkmD
6sx27amVT/4Vr6g8p6SZFRISggTivBvoAz8IWSiJXDeb/CrpCEtpac+46/PW9TRB
8D/+nXyIaoVmcp23T+Mo9nbYEgoL9EfVi3Uv6BXTDHb3AruKxINWrmimvF4F+9rm
NHv8prBIFpennLOpHaQV1p5Dn74rL+r8P1a7aGjjk70v56TdN9ruwU1lrjjvnRS7
Nd3w62YAiyh9z+BvxGy/GrUATAGJLhmltQSeGhn3UR6aQtsOZUHbehD8NXvwyzVC
cOJV0yDuEisldPnni20qDBkc3w6zfaUwlR4oAfu031lVk3lbje62q2GJAjMEEwEK
AB0WIQTtm9961qVeIy6EUkJX/5vbzDAQCQUCWqA8/gAKCRBX/5vbzDAQCYrID/9R
04RUtvl/AS8eSgeJyun/ZdzOfWf7mpzzOIZCICZgMugq26h44nEY3AYR+M0C39/O
q9RD149DHSO3UbkE8UDzhOOBkD7yCOuWQIoPanTiCrA3yhA13BwRNJA6H+KEo2BM
4yhd161UQJdLlPOBfKAxLrQWqP1D4bHdYZA2+4EsTXyIpd4NEOIq8I1DyU6yw+HE
9i4bdCKIDIQO9Bv/xmjzn7Q061jbUcsgCC1P1xiWYA80u5uG95/gNLEpOXJLYd3l
2hjUWhFsQD0TyJfXD/+eTT8XQiMh5AicbJspGhnFYBJkuSdS89FiHybgOzXjhh4r
0nEU/YWELhbYKsG15sr539k1yi+0n41TtGGcirwWI2VpvBiGNgJkFjKM27jgsmF6
fj6ic6pzKJXc88VRy/cf+xWTeD8WJoZEjw/R+SqxGnsiCHNuIIUZsAJcSNKDEpIf
VPZpKePcNpOH8M/0/zyhGNtdehTaK+PC82wVtZxW6We0C0W8PEN36C0PGSdyJTYc
T4j9gxukbz6eZKfm7nP3CWqwS7ETK7Xr11GhG86EYhKU8vMBDbFXdT9elee5DXMp
9yo9uLSWl8/Qpo0Jx31u4RE1p1gQULpR/DkM8PkY0TkmJdP8rVL1Ct7gV5TX8gfX
nINBoE7B33EpYfKPwNeiZtGxitpTK9kNc+I5OqDHjbQqR3JlZ29yeSBTYW5kZXJz
IDxnc2FuZGVyc0BibG9ja3N0cmVhbS5jb20+iQI3BBMBAgAhAhsDAh4BAheABQJX
RxWuBQsJCAcDBRUKCQgLBRYCAwEAAAoJEGviztFKmRe8oV4QAM8+ZVy0UnLm+4SM
zi+Krqu3m+H/V7CVVBo/qY5gsLOtI83cD582Mjv99BpQXqRWT2yKAOt3bwr8nS7l
rigtm77TckeSMDPVTKBsPl6yQcCwlLG3zvDIZFepe9tCMJk5zZHYVJaUzr/PQR3I
Elv4AeVBSyygogYC3y8rl/DK5qFnkVvsIZL+CnfYWAtv2Py6o9xKbG6HNYJ5dNLd
szI94tKdPUf3XcktpqX1KtRLyuaUl6gFOzJ4qD1HZJ2wlhtpwQQeNN6Zu1cnFXMX
BPC9n6rrQVeDBJ0OVIZVMvpFU9fX8DUCvnBxVg0ZQym3aolvG3+RZv4jUYnsIwcT
aHNEXB70K+kQYu33J956l2nCZOmbCGOx3K5s2tpka+vB5LQxJQ+Sz6+DfempFupX
gnG9a0BwhLnmA7YatOxNWZpn6YdCe9BVZU8/qiRec13Iqu/Z3gsFSkP97MSLh60/
1U/2NAgHdvTxctiaLJqCndOZMhLlrm+VQUUKaBK4sy/Ks3xgeW67DtzwuWRkzFn3
o33ewuAfG342yoSaLOJfIhYenIAjca88nNvhxCDX8wzBIVNg/6JRSLWNYegWpeYI
kKSf73qNWs0Tp1pakAcyx6kfKUDb9UBVPInve5feRaQ1fCqynNVeBvyU5MY8erAH
99sdoFfNcLazxzv4z3WfsVm0VTNEiQIcBBABCAAGBQJWCoHvAAoJELGnDk+NzQNm
SpUP/iJTeS0ewm6W7p8JTrtGynlDHmb4kPZFais0ZoHGX3T+bBI2V52Jii1k85x6
m3uDFAxEySYXXC6L/aif4hJEN/30MFhnClSfbUFAnTASCk869m66JCfJySj/or7k
ACbvzXXiN7utgmrH+pjoYgjPL38Tl+gnUFzRqHoN0+4ac2Tv4HCC4VEpyPP3Ba9I
uNrUN98sgmYrsiR84UAKm6Fn/BUvwzVuBNHkmLiB1GotzjmApchh/LXzZdiG2EFH
S9Zecplt5MHKueXeQcCL/uE+6VvU4Bn/o9aLzk5j59Z8cWEjn0KmoItwdco4ki9w
mubUf0UiJe8bG8UkZYpmjqdGbqWaLmpPU8QRaByCA64drGCB1SFToVlBsJHQ2WNc
Rgwcuf/QbO6naFpIXVQnrBnDPnZ3idUeWSC/lCEVTUou6QwFYhi3OtIal5t4Ienm
j1JJl+7yuuCwA7tHPQvYO6sqp8vRGN3nIwNvTPpWSjiZg0Wo+3sJPGZ76iP/zqIx
uvInJoxJ34jFL0A5/5hZXyn6keniaMldZ9e71yQKPTK+foOOXmPWOJtUzOl/BwLf
hm9cAqmuMoxlFbl0Z71yESHEnFXbGwyMkuxKRtkcpmA/xxF3XGzZs1Qt8zMofoUL
iXr3BCq2ryko5GYspVYh0aU0fCrgmHpRiKN6atK1DLyiXehIiQIcBBABCgAGBQJX
Rdt6AAoJEMUkKhqzk2UXB+gP/1llVVQklWi7rl8vqrRf1vt8GRhThrHm3637/PpW
fEqBBkz6AF7A3OngMp8HtWDh5iI7Psbg8LoADLu0PsTYJZeJxLyRilR1XFcAnmfK
KizSadStqkjuLsHdsGRcw3+/SRL1BEkxlwpGMs+YQwBhkAf5kbKKmg42HAaecMRn
qqYrhOIDQye72IXvi95bs0bvO2en7dkGy0BmwYD+wj0RWQWPjXNWdMD27cqRqjXy
AuInJ6P0EENTRMM36Fz9i0sDQ+mOmdAiNqQRJUyPjciWF8UXXBc2LvdL9hrmAffH
53dWcXIcUDs5rmFShh0trFTsWs7VDeeQscPkNh4eFMETp0zrJm+D7XtqfEiMq2cg
JJwTj0UA+4GHKm4XletwaKQCTeDCMIkp4QxQ30y8EhhNlbNICn5bZ2Dkxnt1m2AI
Ps80V6pLbgx2pgLH9va7t8VtzzY2uQngmdwMM4q5ELAOYwznny2jQteG3aOL64Og
wreBk1ppD6Gtkd2qVhgsHq1aFKYsCw3pnJhOZuyajlARLHONUuJIfe5JOkl9f1Ur
iFspVz58UdjCtWTkuBry0NzNwp5P2KeBW0JDNDiechg6FWHGQ4usxShC+KsVOgzD
YRfR1HmzzLY47uZRZ5WyGDok4NFHcoM/AfacIU4mtufDzLgXB9VD/vBrHreyHkdb
KnwMiQIcBBIBCgAGBQJXP47DAAoJEMDAdhMv+naVYhgQAKmiRnDNzWsKa0j84W30
v0tV4sdEeQc5Pkp2aYGqYWnTXfznLuV92cbeqgKUBvk5+Pe6IsNPEGeKYm+wrHFi
XePSW0EXDeg9Hc5NAIGbaxrg8mL17K+2svxDtHqRpDRZUuJcC5lo0Ym2a9v3wWLb
/Vl9o7dYBMZNt4J+BD+23kF7XIIMfs7/HCvndFeiwRAi8dEkq4rMWeov5kn9xlQC
+sTZQraXPKTtAubGsbrPgcAv2wAOZiFk83YShkMNjjOp7FFw6DjJRIDmnn50goZ5
rMziS12dZhFL9Fg8syc+SeBP7RWaNHZ2ajX2my6DPvXF4Oqo25pdLVgHufWG96nV
+szR2M8wRSdxs0kzbR2AMpjBcPZvQBQldCYPwZWXNVZJe/5C/wW0Y7pN+3oiQsDS
9e6HWOZuVpxDuw6HP/sF0aH1mKcSVQ5Hm9crfFgjO1RTHqU2lnbs5dKLsT5/S9s/
MEtZpRbslNaOKmuAU7zouCReUXjc2d0/ozWiP60iJGoRUQS3bhfDtp4DUuYz4n7d
eZXt2e/dHUE4vGGhDaxk436lcC863Iq7k6vybBhHYQgWimL4cMln7fYwWzPvno6l
Kcd0+KTXyeV5IjVAivi4mJFHiuI911c5mUX7bGfYN6/2qhX7weoBAuEQ202STNvc
u0aDkr2B381D7qcxU+GRa2JLiQI4BBMBAgAiBQJWAtzIAhsDBgsJCAcDAgYVCAIJ
CgsEFgIDAQIeAQIXgAAKCRBr4s7RSpkXvP6vD/4uTBFGfETfLFVlVAdLE+Q0uAth
t+6Ce4/gVxX8gx1hwVnLb9bA0PdaWfdqggelKP59hbCuIlNpOiTWqO3vOI6syU7J
D60O0XxU/4bTDTyyKg8df3vyxaVdAGwa7xC/jhJZ98LdHH+68Cq7sPASJ+Tnxj3r
n7+QA8/CMwoIP3EXZQbipKUpo/2hNxHOf9FcvThZPFJoirl8n/7QrEfgl2oSryyc
DKl5SirGUZcfafs1RrWk4JOce7eDmK1o8AtYrnHQKjGOCZhwGUDjU1QPJT4yAD5o
cRDuPnrl9Q3VNe72Hsp6JirJwyxv1X2JddyW8/kJZ5Z774eJMvmMM2CC9buPMT7l
ZMeaCEp2xLUcmBrFgRBbNySSaLzJpDMeMPhWPuWL2tTOoaSpyIVcRII9WmaWrZO9
bFq3p+x1YX8r9gklNc/qV4ECrEM+YH66AIcEq6e50oBxsSShgW9IgcWocoAMwffU
QhA3vUzcEaa7fEKoDnEpxYlO+3NZHrylV4jQLnLCVCuoXgOX4c6yHZ0k5XPPTYpr
9GnxGRzc82O158gm1m4zJYeFnnF/k/Vtyg/MrGHVm3vIgM27NsbenWr3dgaeGGbf
q2XSf7mAYZNIdCFn0OZ9SHaT04JHCYvafjaBa1MYQ1JaNsabN+V5jB1AAt9MjJ5h
UE90il5DYjmajUqbT4kBHAQQAQIABgUCV/BDSQAKCRB/qxFCZ+T6BB8WB/9MLEMS
UkyhXVWRRrc29WgqjPhOuyNDeFzym96b3Ug5eC7al8hJTWBVtBtegUSYYUTYrSYm
apENsKBfzBivBAYWyfUsrt13ItQYDLelJWAK3N4pZ7kzslC/wG8W58bfKmsxMh8n
5lqA83i9FiHOymDSG4uONFoYZ++RABLhptMtnar0xAKeW97NdD7bq3Es1nObAemJ
D1PKNbhk7qCbX18lxCZJUpIW0jOl23gzs1hhPXnKB8tWjj9rgJnIStpZAqTMTevf
1mnlrWWrOiHPAr8uFbkOG4tMsOD6X6/jm1COsKUWhGgtsnviUK2bMsfYmnHol+5a
7+dRQcoKswS81kKuiQIcBBABCAAGBQJZlzKUAAoJEBdWVzLgjl5B9B4QALF0+Ylv
iBCzSThqIV4cvI/Za09X9I90DxA/1K5hTWY5PIQ7mXAcB8PcP8OKXphSMSEcYuBx
CACl0FdMveZgKGOQVvWrHSNoXd5k4DfdE4viVL7T2q1sJ7/c4KnKX10UGDSKJukC
S4xFF1EVGQ5Gh9VkGO54FtFQ22kUbHDzqLVcTQM4dW+M/vaJqiVQH8p1xxOJbd1w
duiqp2nAZwxe2vPaV9wMBMU+VqqYcoBw7sQ4azF/39ZPLPNyLju7oOHhS2MnZ5nO
bul70Io1Vokp9idQ1E+GqLKaam+FOPmiN9Qfhx+D3xkwFNfEHOFDMb0yxnaCsAlm
EOISvq8fBmnh4LNm0Ms7SGtudyNZCBmC9nfG4Y3mBFcMYj9DwgVkV7D4Q2Cgx0OL
HAivVllqG5h6GYwKdCAdogtSfk5yViNfDUABh/XtTaKJ7MP30UHI6oi5YkFuo5L/
NyNc35a86VG1shGlj9CiPeFoKaveIMrr1udRlNnPqrlUsoLpg1B4Swn4srY1ti95
K0mFSsow0vsrQjzpnFGcFmA2awqwkJRFlT4/xDxyn8BHoGAq7HkY5itBIHE1+9Vx
bxF1D6GRzt5VkOc2SzTv6HMiw9tU4NLjGo2hSI3I40ksP3yS60GNg8U+JCs5z1Xm
Sc1o4akB+F02UuaQfIrUZrdFXxs18WV3bSuuiQIyBBABCAAdFiEENfStpiPrn+Oj
vH72e6A1yluQFxMFAlqgNXwACgkQe6A1yluQFxNRmQ/4y5dtLcQ34acImV3B97y0
vikvah/NymgUVd49K+kI8EYdf2/lIIm/V+mvkW/CvgCpzlCMXlhvzhAQFYwjQ3SN
iEWm3lPT95JFsb/A+4t5ZndTG21/T+4sR/3E7lOlAifsQ5N4rItLNfGol/HcF9RK
dvGB8+fByeSOySAOfF45clOX1aEjW4fA4mxvdG4+MDKJmw+SKx59J+5dyIyCwBRx
nzxefsNVS/8fMu0bR3HTSzEE6x0Nb0thyJRfZ/D2gj9v/szkRTpq4/VdblEq6XR4
/MYlAdAPWD9qWoDaLTF0M6v2fRu0ZYf0jatG4XK9v4K5zOyDoMHGs78y1otbVhBd
/rfrpda19vInKn4yfhFOz7D/uqu+75r33E8/nxIgNbtf1UJHB1pB6/nI/aLIK0pD
v6VkHYCtG/ZVmH+f217BHKlHajJvhrKe0rARxo+1U0BrD2NwT9sWeSytoTv6hWX0
Ck86v7qN+ZBSqEvxURb4BIFdKr4erWa+HiHmxMYjHsegFABsautE0Sih3mLiAOz6
Hvu/wlFQX4tIOJiekLACfXALJDrt85k9TgFyhA5vdzMy/vAjKB21SglrCMZG4OTJ
uXkGeGpsZWpIv7vzHev3Ljv8hBpZKs8TkA1MNbm3sBYfhNq2bgQ17uFNO4OHlwpF
726PJMbvhYJREl0h/ZkMEokBMwQQAQoAHRYhBA5MoSvha+aRVvVAyZhPEMx3Fp/S
BQJapXi4AAoJEJhPEMx3Fp/SXloIALL8WFn0I6xVOMpXya9K4emVrpYop0Gh2OuV
hjzU1blTT1B+w8/hz3E+gisR5UuYzSYOo3eA8sIlcS/lb+BI0UsqSY5aQAckBs/4
z4zeLlyn2gwN5aSwpMh4xsA2HhdRAuNRnHq+RAO6Xqj9MyiXg0sZgcnnJty/ERpM
f6wZYpRu0aQUG2qWH9mKsPJ980/qernYpvpC8ji07f0AXtlqP40fWKi0O9Wu589B
YrEYd+zF5bAokkT69FPHC7Po+GmJN55sEmjpyPR4nyC5A00bi/zIiQ/bRLH08OgX
TokihvnsEqCbDi2i08eLwA7Mx+cNAYvNR94S61CzNbDgz6jhVGyJAjkEEAEIACMW
IQRgaFswqhgzq8lGLChf4I0aOmH+zQUCWqci1AWDB4dxAAAKCRBf4I0aOmH+zXop
D/9m3KH4dU8Us5aEsFW2ovcy9meU7UEEfz+pB92wjmbgn9ANhk0DREDqjew7CyCq
9sQyxQaiuV7Y7iSm9mdfC6dWg0skbyGKvNb8vAyi6h6lMtkXNvKPmqdLXBER+5nT
/0z9i0eNlieyMXcbM/FJf9JHBaDplqwCz8c2fBh9eMXrNzZoqqJYNC7Ry2ks4+6g
9TOZP63pCNXgumzvEc6ggxwbZOQJqHmNAVGCWuxwNw+MzA9Zyal6RtvI5O0f4m2n
SIGiDUgByExT3h6yq1pYQ1Jc5otsrZnExvMtdsd3Z4Qt+msw+Lt6Fce3IPkJvOM2
4hQ1oZswB3cMwJfBy0OyVNEcEb+QW9BUZS3fr/1K48dHVQF8DN69B8jZJWhaPMXL
b86Xjf0jkpPLQpLKyfB2kgtbX1MfbA0Pfp5BMtur07EgjaVelNAsh9S+pfDa+NNX
KsXcATeWvlAlsacHlzDEUU2ctEHIAt79/breImLP3zVBG8XPuasje8Ahlr46roYF
VTbI4v87XBJXZ/o3Dt/IJN6Zyce+1qlGOPn9WKeZtPVKeUDxzVuYh2T2V8/2VLU3
kBI8JEAKaYW4+9UYGudOH3p9JW7OoNwl/GAE30MTgBKErqANe9mmttCk7Eh+cm4k
HaKhsAaeD0DCe1QpFfTUjOtIq0md2AhQQHYUdJAGIL9Wb4kCMwQQAQgAHRYhBK+R
cxi4xC0RJyFiXRV+/Ky8ZIQiBQJapzUPAAoJEBV+/Ky8ZIQizf8P/2PjGqd0G7Cb
2YYsy75yxnOL5E+JNKg6sgZiCMtpMcIqX9xxXJ4P2OIw86c9wVufgYaIMs90qoLV
a81eKm0m6Ak3s+zOtemqohtKgf8bYsfqJxS8E93TF/8CahNOCqb5M7yG/ByAxiOm
HnfSU1FDDOivjnzwFhvf44arE6LpPyjbFqenprNZfA8oO9A3fGgLcEIKfrN20Xlv
Cy5TClSz1452RyQop46uXkX63+UodNxHNZjfZ0oY59k8QZStITlrZQkJRZngfbNG
/qQL3+yJ6TsMVwMi/ojYXLddtSn7Y0uoBuSDMEh4093VJ4W9FkBviBTHnaKNnuBx
LXSB+gF9BjckWza3Zjmb4J/qa491t8owrGNM+31hDyeIHjekTB4ge7DSiOUDjQ2k
ACz1NM1B4hiqyYyEOOjUqofzJyiAKmMGuIB+6e5KR56Cm58nNz3kSicvCtDJd4p0
csuXSv0wLAynR8LAFuGSRWuPmVfOEfubiha23DKhYhDX5/pM11yEGJq03w0UwmCF
q8WG5fvKPbuzOPxhVOyMgTuu8t10nqj3N2kCsOCN2EE91mv9XfVBX/ACKLO4YLtl
i9azRU8SyaCBVrx+UAFvOLOYYgxIccY1O/RBZ9NcM8Xa0YwCBY829BrPZ5gzL8JH
K621vsyqg+WnZc0QDsHel6xNrBvqoTdGiQIcBBABAgAGBQJapvH1AAoJENMAEW4c
h1o9a3MP/32FQruC2vrAp5+Z7Bj9tQFRXoTbbhHcvbYpgcxE2E6GkFZsOwynZPMd
QgZmYpVe4zV/dc8daXc6EBtK1lfjveJktKDJYQP0l8468aRBZ8yDlWYDIVP3lz5m
OcTPB7COjThj0/u4hynfv94XLfxTY8Npagr7BQv6zwQyp9/XQK1RsSP25bJWCRmQ
Ucyc1OMeUw6Cdf5bAtFhnaL9NGmb3OOwPslR2BoiZnQWDPCd+pZusE9OGUkZYs29
uSHIaeK4zXVlY0BDEirTQxRO5kHz3lCOZHX/S4bkMRpHp4b6zAq/acjbjIrl+3VD
d0J7WfRH+5yhJJpE+J9NH6EZm+ObSaXSI8hR84ejwARS4mOXuaEB0wk+tObWxXUe
CPYxx2UfBFB6RMPQXdMKFFnqZ2/b0I74zS7+WnrGbjWlS0fazjISUcL/Mn0CilyY
NPgGiqwQRyay1APFrN6ipcfKmOO5wKqlKCRZDoOLVmBiAVSZqjdgz37WyI4Dnj1f
xfUjkQylK7VxNHLIpFMFL9rvxk7Ik1M7o9hz7BaYXzpPOJCBB/UimlOKSULQD26O
9dL99fn37l9No96/6MTwzCfh/d75zZYbeRQv4ig4hvPFv+rrxZbt3TWUrr98eq1H
tW1n2TeoghN4nA9wiOpSqqs9JekwfLggcvMn1ktTp7qUezBZIx6UiQIcBBABCgAG
BQJapaweAAoJEKJtbZ/giO1YjxQQAKjSifwKVg+FG9LRQ3w/LzPPf07xAetStHzp
7NTK3OHkGHY8Iov9miFolhBk5xwWTlmUxLQwFDDzlSzwkSPXJ8PRyBkGo8OSL45n
BZEPxFXAKKJDRT4ZxBxo1td2zhGhQ2x5sEucE8MfN4C2zaFElWPgcPKXS0n6WEr0
U1geRHp1ZZD/gVoZRGKBj4SonJ5synpcA0JmeNc0DjzOYVIxExBGkpfc1/u9VfH4
yMqwg9o7/eB0Q12KYcaqHmpWY6h4i/YUopUW2XTOM1wVumgAcDi+sxybmSoRfMXb
zr8CwPtahIr/uoK9SvRHQ064q0ZoMeEf0vTWPP9NNrKG06h6mUQWSIcOZPLj1CNr
+iN5slTClrhxBzVG/jc+PjEhpyoC6sP3PdoPezuAzj+KzujpJNigt6I8QHsUKo26
Ei1MIeuNmbvGj7RNC4jCNMFQADCekxOxqC1Ty7MEIK/iQb9/cg8cZQ075Q76lv3+
FYi59x8QT3DwDgReLONvnf5iHtX6302mCDQvr/WTcgdR3zkx0Y45dz8djmHY9Qa+
e1Ew2M3rFJDHmeXZzchpFuGw6fKduIJwIiZJAxMM1mwliyL6hvOz2Wixl0OZCa0E
STKSBpmuNG3KusZIJbunrTJvmx4zYTma6vkcVZgkVnOX36SopS6I1NeIu80fbZXm
MRoJ1sGqiQIzBBMBCAAdFiEExCr/fGGz5EoUVM01V692LbM1MyIFAlqheVEACgkQ
V692LbM1MyK7pRAAlOeXKbPti8YXz5tG+ih2gVX9MacDYSIJb7tic5GdBmckN1O4
YsmsaXk+ckXt5Cy78m2psT3aCV+w8fRV9Twny9caA2/ZTRbbSXoENXrdzwlAf54M
jmAWEzvIXLK4hXXbpXu2hoDrS3hZDSrWV6r+7c3rqwBHAMRc4PLjkFjPVAyhD0c2
xIcaPoneexuvgMyeqCa3AoLOtCynZpzGwas6i57M8UvdANnH6uo+NPLR4GB1aY8Q
P/neqyi4+MqhZF8I/AEbYbsufCxpY9txbMJaOPTZHH6aqlPeEPxVpnXxIpzk9Xdf
rouTfzNE2X3rP1FHrOcHYkb2mXSuS/35g7pnAltniheSJfLY/wj9npGrZW5JjC9c
FCe+yf8C/sGmjnY4wK3zuU/8J8xXpua5cFchtIRMcgy2CitNZ0K9ZcYR0vLPxFxc
yI+HbhktKr7J4VBQHkqDCctPixJvqhT9/V/QZ0LU+Qi5vt9U8uqiBljY9yDmM4Ca
fhTGYMvJSsYeGyNXCWhYMVjXjqdGcuFwkdC24ErUU4Rxd9h43l91dHtRFdOlNcZR
EalIVjvkq8IBo1Wu2QisppmkopR608lefbgbpTeGioZ3s1eaW/xY9Fjqc73COlFC
jiBHgDvzJVkWfqlJvkCOxz0zirkhFrp5ztqQTiVpZ3UNREwqzboXv1E/O7OJAjME
EwEKAB0WIQTtm9961qVeIy6EUkJX/5vbzDAQCQUCWqA8/wAKCRBX/5vbzDAQCUYB
D/9uN1/epavezg3Ngddzxxs5VHiz410Li1ybyEx681NbbmX0cfBFEOQmUT6WZHzP
xMXatLIoNOqvYIqXintZ1s+xI8Pntq4LJOSgtCEFER5eshU0AThsrfO0FGKq2qon
HCt5RZ+1A0WsgyW4WXyHCStm0CNE9Pw0SFsc4/zrYlOYBgTKBpZQ4rWYHZo3ER+2
8vpHKijyUmMAgpix/HhL9sTklhk08/Is5oCkO8Ds9LVBv5G0nZYytUlLTp31Dfop
xsiSO3FI4MXFATUlEgdhgZwKiOwLi+gJ2h5i8d8oBstZ1q2zr1j1IQXTrqoogEvw
LfbfZ8ZvjtwM+UgbzE5VauON06RSHNDtvDoTifDj9or3u7cawtiTgVJ35H8KusTe
BlC8KAlA7QjLtdGE0IhqTfY3NzE9ni/KtPc4+GMsNCFygOuZq773dqcwekmIt7Te
I8dsskiz1KXmD8BSThqcXsx4ZbxD5VlDsKiSaSiNHaV1I28Mb8pZkSMv6r7PL88r
PH6+CFiMWTrIoVVGXoqQFm2Ka6nVNPGAOr6dv6X9uCRwfCu01O8fxKFsK9ScwZRr
FIfjJ6eH3Nige6T2SV2Jf29WPqAlfkJNUkUcj0GgbbS2sY+LiE1EALpqBH3qb4cU
gjzW/yAWg9u9DTBlwz9sXkPpHcBh/W4Kr8ypVwQ/zcaNs7kBDQRVp/OLAQgAprG5
CHRskpBJrg4pVUrjtPYF/5sOFi7zGqAhsXsmWTKLgRPmb7VKaT7hcDoFEMPVgOEX
WGfK1csf2qCoTrzCVALTTDlVYmEXctQ1I7O57bQq56RF1yzGxpydWQP+c+qpvQD8
Sax/xqIyPmtgrxU1BQNwB1sudjyE8LrXy2ZKM7/8LNwbZdk6+mNP6QyYHMN8XklU
1cZari5rXMXxxxXNOVTToHYDI4P9B/NPpPoa42xIJ9kmwfWpzT/W45p7yAHwOHtR
UZo0B6EyqC5vB1cOPBVruBADXi5tWTCxPexN1mif7iQQw3ibLXgmXjwRyLm5L6Hr
5yJc7OTjNIXdw6ooTwARAQABiQNEBBgBCgAPBQJVp/OLBQkPCZwAAhsMASkJEGvi
ztFKmRe8wF0gBBkBCgAGBQJVp/OLAAoJEL6KgWWH/15frhUH/2TC3enmmt93W+Sa
4yVwBSYzg0xLyi1Doa9i15xTisj9qG0SlC7erDPv2QqAImF4E+/tmWmpUOcJbMeR
K/H8zhYjkJXw3RJRyflFt3c4PHTDYcSKXj784TFNlfTlqMFDLtEcKVOq1OBTMjnn
di7a8cTpfHBLA4GUFnh+0Ad20hd4NP4v4aDWyMhgOg5XcbvC3GoMIgtwsoM3spbJ
wpKDT3DSdfI0fRgkxLCjh+AWtGDMU7jfNgwdP8aoO/YrQ6Xw/SleoaFXZzopJWjN
ZnRDMNq2okzFCCzfRIRif+PRSxoffftQ9MKLgm45Je5FfUNX1c0EhPqolbe7CX6l
0J1y3bLNuRAA6ZoMbFUiau4xUwxlTT+6P3tZ4I5Yi9bH+PgRTBpdvQ9ernvRqFr2
jdAHmbq9yXs+3Lggqp3STwNvthnnsnusGbVgqurOnduNGZHz6L2DLx+3fdXbSU5V
BPuzLbqemEgtzBviHM5LH0rx5b6vA3KnKfIIC9pKlCJNfIfGR08hP/n3eT4jmK/J
RIr080Y5MqB8JnDFgkLJMOlW8AtPVWQvVXPbjfk9LQ179D1oihE4hocUyO4x9eWY
ZwGOhQHUyTwv06eZ67p00COtnJ2Cc0d72R3ddHrQwQ4eN0KBXBMq/VrJJdqU0qI3
Vp04SO0mO5jcvo9mupSUVYM365fhqYKbdNrRTIty11A8Dgghv9QfasFSPHj8cUqR
d9tAlH5A4taV8180bJP0YoNxVsOWRjgXQzxgqi/DDklLYGBD5Bz7wu1U6z7xdkti
0a+m5qcZblsExmCiMcE8DhsNgpNBRe7mUaeHd7ETw+6guI/CzWWiEmwrEB0eJ6GF
WyX3VnhKR09U//mEpt3C/xk0faG/+/cUWuZRS8UoVAojPuICXVAOgGD6iUjRBnU4
tMQkbcvxR8OTG2I5TGPFdKCnBNULQqFUQrxa4RffKHhgdPdes7dqRR+kqa71tJ5s
kE3Lqdc4F4FTqUPuWzLjOd+FonCABF3+p+dRr+6917hmnqCvaMQpnji5AQ0EVafz
iwEIAPM2TTfR5kMdWMFNLYEzhqE9SByXGT6PAezjn46sGBfZRNyXAInny5uDDbAX
bOyy4YfoUNlNqP2u5w7xnSD4CENl1sihQQ79IkH3DraFT4yJB2Xpe6WITm+T8rYE
ymd/w4AbY6B4/ZxPLjaknzweyyrv8uP8hR4q2V97S+JVryauCeUsvHsQv49y//1p
Ov4c979PVnxata0TxuLa6+VkuFYVeJ4LhAxw2pl3FMoGZuGbiLp0D0krHqXcRPVO
8p7IjV1C0ZSDvR3nabEl2zJtxI2Q4Ezf8XD8chFBlNM6jalDjmfeNhANOmCKhE6E
EH/P8NNSSJto7mkQ1CLFfK7DHU0AEQEAAYkDRAQYAQoADwUCVafziwUJDwmcAAIb
IgEpCRBr4s7RSpkXvMBdIAQZAQoABgUCVafziwAKCRDz9o4thqSP2x1yB/4tASi3
4p3qznwqB9zeufIw5x199go6tdi/uSCgw8e8g4kfcgS4SE1Lne5qKd0OKvevJVln
Di+B2u0QWqhbgJl/MznIpdH+GOYGGb6b/VnQloJSZctjfEZ3wO6htxpjUSAdi36Z
JbMbC1Rt2XG5yeEYdXEufjjzlar56Wub1t1IqkVhTFEbWCMSeUVPPsiZ7oWzdNAg
T3GFAf6qdeI5BjZj4FzNQb0Xmpnf8O1fygSZ+DxP2obzuwRoIqAkmLPMmu3ZpNX8
ZSfkqnNqDhVL1EXipkAStNTCQElk0phOtxYiwiYcl8yqGdIDo/nHYU4Ree9/u1HH
l416t+g2HB3MBdPo4zMP/2g9NWn/Ew7UduzngGAcsPqTiepVilRR+7Ep3yeQc5s4
CiR1SEVjV8BfDPupOHERwgZdMMQziAe3AQGGn29QO3sQnQJDLP/WwDABIuCE4N7p
Yf1lKm6oJ5SBQAc1BzjqqAV0tZSrtIHYarPCcaJBvs8KpWPpvR+XszDkk/2TfrfU
WGBJCUmazSU/gyS7FzWLq884PsbGugJ440VZHnn7UfMcUKRpaPzjUOmzNhuKw8Cv
9f6V2JL/zTrOhtf4YXst70WxcMLGER7zAGu0k8lbm0aUV48CE7fmpseQUST8EOA2
UAEzewF2ejzajsO4v6V/kRbxV94qbZhjq/lpr8LGYEQmEa4MrEqbDcpuh0AT5Sz7
N+YLuFbTULYlRWr7b+/IqT4h2aoRoP0fySWDIudP6YI88GhHWKCA05SEQbJiF3ik
f4PA3WSvVRHbX7MG1BQTgqzPIYKWJcUp54kouIsYr5xdYTmkskjCNbL996cyXlF2
Tzr2VhMvOOUqPaChY7hXbcwfB7B/DH4vfmX1gnWPRRwhBR27xO5ntahvYZQJcxX2
gpTTPn5RJ35yoDRSitb/eaZ0UrIw4OxAYkU3C51VxyAnDxYaq8LBWpz7h0jbPCNT
LmUR3edNAlDWF/cBqdcS6LBA4wrbKy/Xz6glNivztxxrBeg18QMg2FK7NEwGBMBV
=bKfQ
-----END PGP PUBLIC KEY BLOCK-----
```
## Escalation
If there is no response from the authors within 3 days, please escalate your vulnerability report by emailing security@blockstream.com using this PGP key:
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFv/XdQBEAC2iS1uQij2AJSnvQZxScnqf6v0db63QDbS6GjH5PndQ8cF0szv
YJYCFBigkzj4BkKxbJJlnfPW6Jl3SfzCGDvBW3IYuB3S10InDqJFYcM1ZemWCGAs
HA48NDfB4AIBIFH09H4dUE/J6yAdhX/+Qa/bjOhiwrCFVE2pVtMN8aTFnaLzxCP+
fWZUaPrPv84B7uxEdLM77wIhsN+16FAr1qS42NfKDDolBAs//Bmv5fkNC7lzAVCf
MA/QEcNlAvButPrNyZU3t25maUv5hhKUDdJ2G/iACf8tVgp+ygmD8NHQMLPSaFqa
O5wy77Fd5OyX3Gii/E8MtPEsePViwecwJqc/3UXBx7zTRou2gxLikVFTnJb+Jit9
F2kcljhCjHGxsuhf4Zr6zu+RTHHDgdBmpt4t1HA2jft/40r+uWQjL/rNP+01HgZj
4OLHkSI5VfJsXRn1EqOGpBIzR56f0GaxA0jluQMfkE9PTMxg5+YbrGgdot3l7pQ3
+mqMu3aim2EYZZHTsMCRt4j4pRn5g4BZan+w7STfA7rIMJu/MjP3G4s+IFMPVRki
QLwktZSD+x2M9iIsOD4YVheMKtU6WRroFeCkXzIzLYwCuZ4ym/JFJMH+Keuyo254
5hcymw+ivmPP+xuuoP1npQioRH4RKpfDgskABv8+t5rteV4BtUIWL33A/wARAQAB
tDlCbG9ja3N0cmVhbSBTZWN1cml0eSBSZXBvcnRpbmcgPHNlY3VyaXR5QGJsb2Nr
c3RyZWFtLmNvbT6JAk4EEwEKADgWIQQRdlQtqY5x4TNyLvdKyMyIaESi1gUCW/9d
1AIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRBKyMyIaESi1lcQD/9HZmtP
XhKtwC92zTsT5Xqt/K4ckaiJRaUlHeFtfkHpTdXIUFIJjZ1w1JJAWLtRf58MY45U
5DAOOYQptoXiy4USZkIMH1uBtFSAvyCUXH5cDWK1347G5rUg6Ry8Cxe+wzXOlxfr
f/9Vs28z+awfIrvk50sj4QW+mMlS69VwuHUl5CJ+BtcqQWQO85ummQxQq8rMw7rD
AwkftqiMKz+YLw5/xECyiXDDdQr66kdkglbQGgiciS7HNo0SQ2XqTNcGZkRA3lmv
HYCchZpgr9qxfnLjgVddJB+iNTwFZ7AQ7ZBlYWvu5UIMweuEz+yB7WGbQZLOsRZ8
OaIPmZ150VX0sQYeXYhoFrraNW6obFqsSklnQbsfw6KsCaFvYhNZgHf177YlrAzq
puR53H1sOjOQq8pnbjyf4XLhAGMC65LydWtkQK77m46kOBZad9UGg2WKg/SY+3pF
WWdP7vlsR7oJyElEQfUwBsT16K/6kenyagQ6CzqnF/X+W7P1STndpBJp4lD0RfaD
v6UyqxPYhUuQ24jP5jm8+RtS+OGB00czY2cVSDgjYVuU80WsW+Qt0XtLKeoVYdCb
TaKgreicqbz0Afr9hbPIieW2wbQnYlRPjprTVhhGsxlaUb7Kcz7fapliJrKBFgy5
odUljZ+iSompuiYtFhVYA8e6sx0pRUGOopnkGLkCDQRb/13UARAA3WAlRv6DofgG
xu+L2ePZb1OCQTkn4Eq+24veGibPvlqFJivF1ebctUtxiKVsz0dXtWcAYk7Rh2I/
xsEGxIzhjr5VLVOdldM5AgJna6WPvOA4sPXjdy47R71NfEQfg9Svv93mmkpbJsL3
NuHxvpoeO4A9JrFfwn7WJevXOiUWdKJ+nn0ZPwjYle6i27OfIojyVmZVQEiHC/Il
LxQEYaNDalAorjnn0b7X7S3Z8pMAb8HqD0RTXXed9LPgbasARyND2I2xy1txUDPI
Qcq6tIbryGYlegEHuvsE31zRPoNjnXkwABb6qBkUUiZMbRJCYOQXSo7Z2tasKHIJ
I/FnIj8dmT/IXDb9KiWr8wziGLdgnZx3QZGt5P0LIMFKrfXMNJO7EmO1QMbgZFgk
JPhJ0o61PvMaVLMQVoxD6K7bKOzI2t4LTA0l5RxuMcadu8G13YzgVXX44Cac1qUn
xriMzk62HXdSeZozcO/IRN7Kdw2bB++5EVYTQN1EEhIymXVUrBg2pXvLSXalg+kp
0BhLVHcbTI51mKz8GY9NUShFI7ZEzxzzltcEA+F5TLrPMgT+tx+QvjDdGWIhWycI
KW53hjKiGolhpG9Kqo9ogtCO2a3r6JspO0z+54/EF5rS2LI13pqk0qNgoYMYqChe
XU8BJdZ9siCooQ+3o+Y/9TkQWSAwnWkAEQEAAYkCNgQYAQoAIBYhBBF2VC2pjnHh
M3Iu90rIzIhoRKLWBQJb/13UAhsMAAoJEErIzIhoRKLWGhoP/jFfwRrda1RNR6OY
NHOIa4x4PtjDuYwDYgI5X2NQXlglyOTWouKjY1eu7LRoQSS5blD7BA9GHhYRDBL/
0NQo/EQn3JFoitGWs07Bry0A4DTOz0H7wRqVXtN+Ck13QdEemq+suLE+PcbRJ4Ei
ANoNVgSRGqYO683oXEzGgzF+FXXPbcRTNHwvV8LgmUioe2cgHX3Q2PC3gUTmnNkq
IhWirlT5cQVSLS2IzsP903uq8VtHl7lXLkS6Ba3CmwLoHYfhurGQNR6Av2WPgL2D
oY8NOxPdz9QxBUzUVObiMm3UfD/eTF73NAmNJRDqYzpY/l54ZyxLFjlfXRpwKrx/
islwezx+2fzns5u4xwdywVHzvgsmbXMIDdNTaTS8BDaKbAopLmbmuTnnTbJXWFbb
mQ2/GHcB0mKuXDkzt+7JMQ0NHtrGC3qvEtnTXZGXr3uIhFDkJSOoaH68dqq5++pz
GtT+aiv3L120r0pSSyTgbPsrqSlWgXEuJ4uzt3j69J0Qek0YrL0EDxHdnGPW4+fv
AZiq1RFG8MHOy0Obahed5uqlzXCNtroHdgSQeR+6IkODSsEd+hVdXJs/hjcWLNG5
VNztar/H4BSwlhKbgvFivOzhj8x5TNoqMM95G8Ew/5idiT/YQgsA6lcwsEZ78t9O
lTHPj4G8vH5F/zIFb+uQNSlKzuH+
=8mAH
-----END PGP PUBLIC KEY BLOCK-----
```
## Acknowledgement
Your help in maintaining and improving the security of this software is deeply appreciated. When the vulnerability you have reported is patched here and in downstream dependencies, we will publicly disclose it. Let us know if you would like to remain anonymous or would like to be acknowledged. We would be happy to acknowledge your contributions to closing the vulnerability you found in the release notes, source code and/or documentation in this repository.

View file

@ -256,7 +256,7 @@ if test "x$enable_debug" = xyes; then
# Prefer -Og, fall back to -O0 if that is unavailable.
AX_CHECK_COMPILE_FLAG(
[-Og],
[[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -Og"]],
[[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -O0"]],
[AX_CHECK_COMPILE_FLAG([-O0],[[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -O0"]],,[[$CXXFLAG_WERROR]])],
[[$CXXFLAG_WERROR]])
@ -1459,7 +1459,7 @@ if test x$need_bundled_univalue = xyes; then
AC_CONFIG_SUBDIRS([src/univalue])
fi
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery --disable-jni --enable-experimental --enable-module-whitelist --enable-module-rangeproof --enable-module-generator"
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery --disable-jni --enable-experimental --enable-module-whitelist --enable-module-rangeproof --enable-module-generator --enable-module-surjectionproof --enable-module-ecdh"
AC_CONFIG_SUBDIRS([src/secp256k1])
AC_OUTPUT

View file

@ -92,8 +92,12 @@ endif
BITCOIN_CORE_H = \
addrdb.h \
addrman.h \
asset.h \
assetsdir.h \
base58.h \
bech32.h \
blech32.h \
blind.h \
bloom.h \
blockencodings.h \
blockfilter.h \
@ -111,6 +115,7 @@ BITCOIN_CORE_H = \
compat/endian.h \
compat/sanity.h \
compressor.h \
confidential_validation.h \
consensus/consensus.h \
consensus/tx_verify.h \
core_io.h \
@ -126,6 +131,7 @@ BITCOIN_CORE_H = \
interfaces/handler.h \
interfaces/node.h \
interfaces/wallet.h \
issuance.h \
key.h \
key_io.h \
keystore.h \
@ -223,12 +229,14 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_server_a_SOURCES = \
addrdb.cpp \
addrman.cpp \
assetsdir.cpp \
bloom.cpp \
blockencodings.cpp \
blockfilter.cpp \
block_proof.cpp \
chain.cpp \
checkpoints.cpp \
confidential_validation.cpp \
consensus/tx_verify.cpp \
httprpc.cpp \
httpserver.cpp \
@ -290,6 +298,7 @@ endif
libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_wallet_a_SOURCES = \
assetsdir.cpp \
interfaces/wallet.cpp \
wallet/coincontrol.cpp \
wallet/crypter.cpp \
@ -358,6 +367,7 @@ libbitcoin_consensus_a_SOURCES = \
amount.h \
arith_uint256.cpp \
arith_uint256.h \
asset.cpp \
consensus/merkle.cpp \
consensus/merkle.h \
consensus/params.h \
@ -367,6 +377,8 @@ libbitcoin_consensus_a_SOURCES = \
prevector.h \
primitives/block.cpp \
primitives/block.h \
primitives/confidential.cpp \
primitives/confidential.h \
primitives/txwitness.cpp \
primitives/txwitness.h \
primitives/transaction.cpp \
@ -403,11 +415,14 @@ libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_common_a_SOURCES = \
base58.cpp \
bech32.cpp \
blech32.cpp \
blind.cpp \
chainparams.cpp \
coins.cpp \
compressor.cpp \
core_read.cpp \
core_write.cpp \
issuance.cpp \
key.cpp \
key_io.cpp \
keystore.cpp \

View file

@ -96,7 +96,8 @@ BITCOIN_TESTS =\
test/util_tests.cpp \
test/validation_block_tests.cpp \
test/versionbits_tests.cpp \
test/pegin_spent_tests.cpp
test/pegin_spent_tests.cpp \
test/blind_tests.cpp
# ELEMENTS IN THE END
if ENABLE_PROPERTY_TESTS

149
src/asset.cpp Normal file
View file

@ -0,0 +1,149 @@
#include <asset.h>
CAmountMap& operator+=(CAmountMap& a, const CAmountMap& b)
{
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it)
a[it->first] += it->second;
return a;
}
CAmountMap& operator-=(CAmountMap& a, const CAmountMap& b)
{
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it)
a[it->first] -= it->second;
return a;
}
CAmountMap operator+(const CAmountMap& a, const CAmountMap& b)
{
CAmountMap c;
for(std::map<CAsset, CAmount>::const_iterator it = a.begin(); it != a.end(); ++it)
c[it->first] += it->second;
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it)
c[it->first] += it->second;
return c;
}
CAmountMap operator-(const CAmountMap& a, const CAmountMap& b)
{
CAmountMap c;
for(std::map<CAsset, CAmount>::const_iterator it = a.begin(); it != a.end(); ++it)
c[it->first] += it->second;
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it)
c[it->first] -= it->second;
return c;
}
bool operator<(const CAmountMap& a, const CAmountMap& b)
{
bool smallerElement = false;
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it) {
CAmount aValue = a.count(it->first) ? a.find(it->first)->second : 0;
if (aValue > it->second)
return false;
if (aValue < it->second)
smallerElement = true;
}
for(std::map<CAsset, CAmount>::const_iterator it = a.begin(); it != a.end(); ++it) {
CAmount bValue = b.count(it->first) ? b.find(it->first)->second : 0;
if (it->second > bValue)
return false;
if (it->second < bValue)
smallerElement = true;
}
return smallerElement;
}
bool operator<=(const CAmountMap& a, const CAmountMap& b)
{
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it) {
CAmount aValue = a.count(it->first) ? a.find(it->first)->second : 0;
if (aValue > it->second)
return false;
}
for(std::map<CAsset, CAmount>::const_iterator it = a.begin(); it != a.end(); ++it) {
CAmount bValue = b.count(it->first) ? b.find(it->first)->second : 0;
if (it->second > bValue)
return false;
}
return true;
}
bool operator>(const CAmountMap& a, const CAmountMap& b)
{
bool largerElement = false;
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it) {
CAmount aValue = a.count(it->first) ? a.find(it->first)->second : 0;
if (aValue < it->second)
return false;
if (aValue > it->second)
largerElement = true;
}
for(std::map<CAsset, CAmount>::const_iterator it = a.begin(); it != a.end(); ++it) {
CAmount bValue = b.count(it->first) ? b.find(it->first)->second : 0;
if (it->second < bValue)
return false;
if (it->second > bValue)
largerElement = true;
}
return largerElement;
}
bool operator>=(const CAmountMap& a, const CAmountMap& b)
{
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it) {
if ((a.count(it->first) ? a.find(it->first)->second : 0) < it->second)
return false;
}
for(std::map<CAsset, CAmount>::const_iterator it = a.begin(); it != a.end(); ++it) {
if (it->second < (b.count(it->first) ? b.find(it->first)->second : 0))
return false;
}
return true;
}
bool operator==(const CAmountMap& a, const CAmountMap& b)
{
for(std::map<CAsset, CAmount>::const_iterator it = a.begin(); it != a.end(); ++it) {
if ((b.count(it->first) ? b.find(it->first)->second : 0) != it->second)
return false;
}
for(std::map<CAsset, CAmount>::const_iterator it = b.begin(); it != b.end(); ++it) {
if ((a.count(it->first) ? a.find(it->first)->second : 0) != it->second)
return false;
}
return true;
}
bool operator!=(const CAmountMap& a, const CAmountMap& b)
{
return !(a == b);
}
bool hasNegativeValue(const CAmountMap& amount)
{
for(std::map<CAsset, CAmount>::const_iterator it = amount.begin(); it != amount.end(); ++it) {
if (it->second < 0)
return true;
}
return false;
}
bool hasNonPostiveValue(const CAmountMap& amount)
{
for(std::map<CAsset, CAmount>::const_iterator it = amount.begin(); it != amount.end(); ++it) {
if (it->second <= 0)
return true;
}
return false;
}
CAmount valueFor(const CAmountMap& mapValue, const CAsset& asset) {
CAmountMap::const_iterator it = mapValue.find(asset);
if (it != mapValue.end()) {
return it->second;
} else {
return CAmount(0);
}
}

108
src/asset.h Normal file
View file

@ -0,0 +1,108 @@
#ifndef BITCOIN_ASSET_H
#define BITCOIN_ASSET_H
#include <uint256.h>
#include <amount.h>
#include <serialize.h>
/**
* Native Asset Issuance
*
* An asset identifier tag, a 256 bits serialized hash (sha256) defined
* by the issuance transaction from which the outputs coins are derived.
* Each output contains coins from a single asset/currency.
* For the host currency, the similarly-calculated hash of the chains genesis
* block is used instead.
**/
struct CAsset {
uint256 id;
CAsset() { }
explicit CAsset(const uint256& idIn) : id(idIn) { }
explicit CAsset(const std::vector<unsigned char>& vchIDIn) : id(vchIDIn) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(id);
}
bool IsNull() const { return id.IsNull(); }
void SetNull() { id.SetNull(); }
unsigned char* begin() { return id.begin(); }
unsigned char* end() { return id.end(); }
const unsigned char* begin() const { return id.begin(); }
const unsigned char* end() const { return id.end(); }
std::string GetHex() const { return id.GetHex(); }
void SetHex(const std::string& str) { id.SetHex(str); }
friend bool operator==(const CAsset& a, const CAsset& b)
{
return a.id == b.id;
}
friend bool operator!=(const CAsset& a, const CAsset& b)
{
return !(a == b);
}
friend bool operator<(const CAsset& a, const CAsset& b)
{
return a.id < b.id;
}
};
/** Used for consensus fee and general wallet accounting*/
typedef std::map<CAsset, CAmount> CAmountMap;
CAmountMap& operator+=(CAmountMap& a, const CAmountMap& b);
CAmountMap& operator-=(CAmountMap& a, const CAmountMap& b);
CAmountMap operator+(const CAmountMap& a, const CAmountMap& b);
CAmountMap operator-(const CAmountMap& a, const CAmountMap& b);
// WARNING: Comparisons are only looking for *complete* ordering.
// For strict inequality checks, if any entry would fail the non-strict
// inequality, the comparison will fail. Therefore it is possible
// that all inequality comparison checks may fail.
// Therefore if >/< fails against a CAmountMap(), this means there
// are all zeroes or one or more negative values.
//
// Examples: 1A + 2B <= 1A + 2B + 1C
// and 1A + 2B < 1A + 2B + 1C
// but
// !(1A + 2B == 1A + 2B + 1C)
//-------------------------------------
// 1A + 2B == 1A + 2B
// and 1A + 2B <= 1A + 2B
// but
// !(1A + 2B < 1A + 2B)
//-------------------------------------
// !(1A + 2B == 2B - 1C)
// !(1A + 2B >= 2B - 1C)
// ...
// !(1A + 2B < 2B - 1C)
// and 1A + 2B != 2B - 1C
bool operator<(const CAmountMap& a, const CAmountMap& b);
bool operator<=(const CAmountMap& a, const CAmountMap& b);
bool operator>(const CAmountMap& a, const CAmountMap& b);
bool operator>=(const CAmountMap& a, const CAmountMap& b);
bool operator==(const CAmountMap& a, const CAmountMap& b);
bool operator!=(const CAmountMap& a, const CAmountMap& b);
inline bool MoneyRange(const CAmountMap& mapValue) {
for(CAmountMap::const_iterator it = mapValue.begin(); it != mapValue.end(); it++) {
if (it->second < 0 || it->second > MAX_MONEY) {
return false;
}
}
return true;
}
CAmount valueFor(const CAmountMap& mapValue, const CAsset& asset);
#endif // BITCOIN_AMOUNT_H

99
src/assetsdir.cpp Normal file
View file

@ -0,0 +1,99 @@
// Copyright (c) 2017-2017 The Elements Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <assetsdir.h>
#include <chainparams.h>
#include <tinyformat.h>
#include <utilstrencodings.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
void CAssetsDir::Set(const CAsset& asset, const AssetMetadata& metadata)
{
// No asset or label repetition
if (GetLabel(asset) != "")
throw std::runtime_error(strprintf("duplicated asset '%s'", asset.GetHex()));
if (GetAsset(metadata.GetLabel()) != CAsset())
throw std::runtime_error(strprintf("duplicated label '%s'", metadata.GetLabel()));
mapAssetMetadata[asset] = metadata;
mapAssets[metadata.GetLabel()] = asset;
}
void CAssetsDir::SetHex(const std::string& assetHex, const std::string& label)
{
if (!IsHex(assetHex) || assetHex.size() != 64)
throw std::runtime_error("The asset must be hex string of length 64");
const std::vector<std::string> protectedLabels = {"", "*", "bitcoin", "Bitcoin", "btc"};
for (std::string proLabel : protectedLabels) {
if (label == proLabel) {
throw std::runtime_error(strprintf("'%s' label is protected", proLabel));
}
}
Set(CAsset(uint256S(assetHex)), AssetMetadata(label));
}
void CAssetsDir::InitFromStrings(const std::vector<std::string>& assetsToInit, const std::string& pegged_asset_name)
{
for (std::string strToSplit : assetsToInit) {
std::vector<std::string> vAssets;
boost::split(vAssets, strToSplit, boost::is_any_of(":"));
if (vAssets.size() != 2) {
throw std::runtime_error("-assetdir parameters malformed, expecting asset:label");
}
SetHex(vAssets[0], vAssets[1]);
}
// Set "bitcoin" to the pegged asset for tests
Set(Params().GetConsensus().pegged_asset, AssetMetadata(pegged_asset_name));
}
CAsset CAssetsDir::GetAsset(const std::string& label) const
{
auto it = mapAssets.find(label);
if (it != mapAssets.end())
return it->second;
return CAsset();
}
AssetMetadata CAssetsDir::GetMetadata(const CAsset& asset) const
{
auto it = mapAssetMetadata.find(asset);
if (it != mapAssetMetadata.end())
return it->second;
return AssetMetadata("");
}
std::string CAssetsDir::GetLabel(const CAsset& asset) const
{
return GetMetadata(asset).GetLabel();
}
std::vector<CAsset> CAssetsDir::GetKnownAssets() const
{
std::vector<CAsset> knownAssets;
for (auto it = mapAssets.begin(); it != mapAssets.end(); it++) {
knownAssets.push_back(it->second);
}
return knownAssets;
}
CAsset GetAssetFromString(const std::string& strasset) {
CAsset asset = gAssetsDir.GetAsset(strasset);
if (asset.IsNull() && strasset.size() == 64 && IsHex(strasset)) {
asset = CAsset(uint256S(strasset));
}
return asset;
}
// GLOBAL:
CAssetsDir _gAssetsDir;
const CAssetsDir& gAssetsDir = _gAssetsDir;
void InitGlobalAssetDir(const std::vector<std::string>& assetsToInit, const std::string& pegged_asset_name)
{
_gAssetsDir.InitFromStrings(assetsToInit, pegged_asset_name);
}

60
src/assetsdir.h Normal file
View file

@ -0,0 +1,60 @@
#ifndef BITCOIN_ASSETSDIR_H
#define BITCOIN_ASSETSDIR_H
#include <asset.h>
#include <map>
class AssetMetadata
{
std::string label;
public:
AssetMetadata() : label("") {};
AssetMetadata(std::string _label) : label(_label) {};
const std::string& GetLabel() const
{
return label;
}
};
class CAssetsDir
{
std::map<CAsset, AssetMetadata> mapAssetMetadata;
std::map<std::string, CAsset> mapAssets;
void Set(const CAsset& asset, const AssetMetadata& metadata);
void SetHex(const std::string& assetHex, const std::string& label);
public:
void InitFromStrings(const std::vector<std::string>& assetsToInit, const std::string& pegged_asset_name);
/**
* @param label A label string
* @return asset id corresponding to the asset label
*/
CAsset GetAsset(const std::string& label) const;
AssetMetadata GetMetadata(const CAsset& asset) const;
/** @return the label associated to the asset id */
std::string GetLabel(const CAsset& asset) const;
std::vector<CAsset> GetKnownAssets() const;
};
/**
* Returns asset id corresponding to the given asset expression, which is either an asset label or a hex value.
* @param strasset A label string or a hex value corresponding to an asset
* @return The asset ID for the given expression
*/
CAsset GetAssetFromString(const std::string& strasset);
// GLOBAL:
class CAssetsDir;
extern const CAssetsDir& gAssetsDir;
void InitGlobalAssetDir(const std::vector<std::string>& assetsToInit, const std::string& pegged_asset_name);
#endif // BITCOIN_ASSETSDIR_H

View file

@ -95,7 +95,7 @@ static void AssembleBlock(benchmark::State& state)
tx.vin.push_back(MineBlock(SCRIPT_PUB));
tx.witness.vtxinwit.resize(1);
tx.witness.vtxinwit.back().scriptWitness = witness;
tx.vout.emplace_back(1337, SCRIPT_PUB);
tx.vout.emplace_back(CTxOut(CAsset(), 1337, SCRIPT_PUB));
if (NUM_BLOCKS - b >= COINBASE_MATURITY)
txs.at(b) = MakeTransactionRef(tx);
}

View file

@ -39,9 +39,9 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
dummyTransactions[1].vout.resize(2);
dummyTransactions[1].vout[0].nValue = 21 * CENT;
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID());
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[2].GetPubKey()));
dummyTransactions[1].vout[1].nValue = 22 * CENT;
dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID());
dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(PKHash(key[3].GetPubKey()));
AddCoins(coinsRet, dummyTransactions[1], 0);
return dummyTransactions;

View file

@ -45,11 +45,12 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::State& state)
// Make insecure_rand here so that each iteration is identical.
FastRandomContext insecure_rand(true);
CCheckQueueControl<PrevectorJob> control(&queue);
std::vector<std::vector<PrevectorJob>> vBatches(BATCHES);
std::vector<std::vector<PrevectorJob*>> vBatches(BATCHES);
for (auto& vChecks : vBatches) {
vChecks.reserve(BATCH_SIZE);
for (size_t x = 0; x < BATCH_SIZE; ++x)
vChecks.emplace_back(insecure_rand);
for (size_t x = 0; x < BATCH_SIZE; ++x) {
vChecks.emplace_back(new PrevectorJob(insecure_rand));
}
control.Add(vChecks);
}
// control waits for completion by RAII, but

View file

@ -5,6 +5,8 @@
#include <bench/bench.h>
#include <wallet/wallet.h>
#include <wallet/coinselection.h>
#include <asset.h>
#include <policy/policy.h>
#include <set>
@ -47,12 +49,23 @@ static void CoinSelection(benchmark::State& state)
const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0);
while (state.KeepRunning()) {
std::set<CInputCoin> setCoinsRet;
CAmount nValueRet;
CAmountMap mapValueRet;
bool bnb_used;
bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used);
CAmountMap mapValue;
mapValue[::policyAsset] = 1003 * COIN;
bool success = wallet.SelectCoinsMinConf(mapValue, filter_standard, groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used);
assert(success);
assert(nValueRet == 1003 * COIN);
assert(mapValueRet[::policyAsset] == 1003 * COIN);
assert(setCoinsRet.size() == 2);
/* std::set<std::pair<const CWalletTx*, unsigned int> > setCoinsRet;
CAmountMap nValueRet;
CAmountMap mapValue;
mapValue[CAsset()] = 1003 * COIN;
bool success = wallet.SelectCoinsMinConf(mapValue, 1, 6, 0, vCoins, setCoinsRet, nValueRet);
assert(success);
assert(nValueRet[CAsset()] == 1003 * COIN);
assert(setCoinsRet.size() == 2);*/
}
}

View file

@ -97,10 +97,12 @@ static void VerifyScriptBench(benchmark::State& state)
#if defined(HAVE_CONSENSUS_LIB)
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
stream << txSpend;
CDataStream streamVal(SER_NETWORK, PROTOCOL_VERSION);
streamVal << txCredit.vout[0].nValue;
int csuccess = bitcoinconsensus_verify_script_with_amount(
txCredit.vout[0].scriptPubKey.data(),
txCredit.vout[0].scriptPubKey.size(),
txCredit.vout[0].nValue,
(const unsigned char*)&streamVal[0], streamVal.size(),
(const unsigned char*)stream.data(), stream.size(), 0, flags, nullptr);
assert(csuccess == 1);
#endif

View file

@ -6,6 +6,7 @@
#include <config/bitcoin-config.h>
#endif
#include <asset.h>
#include <clientversion.h>
#include <coins.h>
#include <consensus/consensus.h>
@ -46,7 +47,7 @@ static void SetupBitcoinTxArgs()
gArgs.AddArg("in=TXID:VOUT(:SEQUENCE_NUMBER)", "Add input to TX", false, OptionsCategory::COMMANDS);
gArgs.AddArg("locktime=N", "Set TX lock time to N", false, OptionsCategory::COMMANDS);
gArgs.AddArg("nversion=N", "Set TX version to N", false, OptionsCategory::COMMANDS);
gArgs.AddArg("outaddr=VALUE:ADDRESS", "Add address-based output to TX", false, OptionsCategory::COMMANDS);
gArgs.AddArg("outaddr=VALUE:ADDRESS(:ASSET)", "Add address-based output to TX", false, OptionsCategory::COMMANDS);
gArgs.AddArg("outdata=[VALUE:]DATA", "Add data-based output to TX", false, OptionsCategory::COMMANDS);
gArgs.AddArg("outmultisig=VALUE:REQUIRED:PUBKEYS:PUBKEY1:PUBKEY2:....[:FLAGS]", "Add Pay To n-of-m Multi-sig output to TX. n = REQUIRED, m = PUBKEYS. "
"Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output. "
@ -67,6 +68,8 @@ static void SetupBitcoinTxArgs()
gArgs.AddArg("load=NAME:FILENAME", "Load JSON file FILENAME into register NAME", false, OptionsCategory::REGISTER_COMMANDS);
gArgs.AddArg("set=NAME:JSON-STRING", "Set register NAME to given JSON-STRING", false, OptionsCategory::REGISTER_COMMANDS);
gArgs.AddArg("-serialization=TYPE", "Sets the serialization of transactions. ELEMENTS or BITCOIN are the two valid options.", false, OptionsCategory::REGISTER_COMMANDS);
// Hidden
gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN);
@ -270,7 +273,7 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn
std::vector<std::string> vStrInputParts;
boost::split(vStrInputParts, strInput, boost::is_any_of(":"));
if (vStrInputParts.size() != 2)
if (vStrInputParts.size() < 2)
throw std::runtime_error("TX output missing or too many separators");
// Extract and validate VALUE
@ -284,8 +287,22 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn
}
CScript scriptPubKey = GetScriptForDestination(destination);
// extract and validate ASSET
CAsset asset;
if (!g_con_elementsmode && vStrInputParts.size() == 3) {
throw std::runtime_error("TX output asset type invalid for BITCOIN serialization");
}
if (vStrInputParts.size() == 3) {
asset = CAsset(uint256S(vStrInputParts[2]));
if (asset.IsNull()) {
throw std::runtime_error("invalid TX output asset type");
}
} else {
asset = Params().GetConsensus().pegged_asset;
}
// construct TxOut, append to transaction output list
CTxOut txout(value, scriptPubKey);
CTxOut txout(asset, CConfidentialValue(value), scriptPubKey);
tx.vout.push_back(txout);
}
@ -325,11 +342,11 @@ static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& str
}
if (bScriptHash) {
// Get the ID for the script, and then construct a P2SH destination for it.
scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey));
scriptPubKey = GetScriptForDestination(ScriptHash(scriptPubKey));
}
// construct TxOut, append to transaction output list
CTxOut txout(value, scriptPubKey);
CTxOut txout(Params().GetConsensus().pegged_asset, CConfidentialValue(value), scriptPubKey);
tx.vout.push_back(txout);
}
@ -399,11 +416,11 @@ static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& s
"redeemScript exceeds size limit: %d > %d", scriptPubKey.size(), MAX_SCRIPT_ELEMENT_SIZE));
}
// Get the ID for the script, and then construct a P2SH destination for it.
scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey));
scriptPubKey = GetScriptForDestination(ScriptHash(scriptPubKey));
}
// construct TxOut, append to transaction output list
CTxOut txout(value, scriptPubKey);
CTxOut txout(Params().GetConsensus().pegged_asset, CConfidentialValue(value), scriptPubKey);
tx.vout.push_back(txout);
}
@ -411,26 +428,46 @@ static void MutateTxAddOutData(CMutableTransaction& tx, const std::string& strIn
{
CAmount value = 0;
// separate [VALUE:]DATA in string
size_t pos = strInput.find(':');
// separate [VALUE:]DATA[:ASSET] in string
std::vector<std::string> vStrInputParts;
boost::split(vStrInputParts, strInput, boost::is_any_of(":"));
if (pos==0)
// Check that there are enough parameters
if (vStrInputParts[0].empty())
throw std::runtime_error("TX output value not specified");
if (pos != std::string::npos) {
// Extract and validate VALUE
value = ExtractAndValidateValue(strInput.substr(0, pos));
if (vStrInputParts.size()>3)
throw std::runtime_error("too many separators");
std::vector<unsigned char> data;
CAsset asset(Params().GetConsensus().pegged_asset);
if (vStrInputParts.size()==1) {
std::string strData = vStrInputParts[0];
if (!IsHex(strData))
throw std::runtime_error("invalid TX output data");
data = ParseHex(strData);
} else {
value = ExtractAndValidateValue(vStrInputParts[0]);
std::string strData = vStrInputParts[1];
if (!IsHex(strData))
throw std::runtime_error("invalid TX output data");
data = ParseHex(strData);
if (vStrInputParts.size()==3) {
std::string strAsset = vStrInputParts[2];
if (!IsHex(strAsset))
throw std::runtime_error("invalid TX output asset type");
asset.SetHex(strAsset);
if (asset.IsNull()) {
throw std::runtime_error("invalid TX output asset type");
}
}
}
// extract and validate DATA
std::string strData = strInput.substr(pos + 1, std::string::npos);
if (!IsHex(strData))
throw std::runtime_error("invalid TX output data");
std::vector<unsigned char> data = ParseHex(strData);
CTxOut txout(value, CScript() << OP_RETURN << data);
CTxOut txout(asset, CConfidentialValue(value), CScript() << OP_RETURN << data);
tx.vout.push_back(txout);
}
@ -471,11 +508,11 @@ static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& str
throw std::runtime_error(strprintf(
"redeemScript exceeds size limit: %d > %d", scriptPubKey.size(), MAX_SCRIPT_ELEMENT_SIZE));
}
scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey));
scriptPubKey = GetScriptForDestination(ScriptHash(scriptPubKey));
}
// construct TxOut, append to transaction output list
CTxOut txout(value, scriptPubKey);
CTxOut txout(Params().GetConsensus().pegged_asset, CConfidentialValue(value), scriptPubKey);
tx.vout.push_back(txout);
}
@ -612,7 +649,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
newcoin.out.scriptPubKey = scriptPubKey;
newcoin.out.nValue = 0;
if (prevOut.exists("amount")) {
newcoin.out.nValue = AmountFromValue(prevOut["amount"]);
newcoin.out.nValue = CConfidentialValue(AmountFromValue(prevOut["amount"]));
}
newcoin.nHeight = 1;
view.AddCoin(out, std::move(newcoin), true);
@ -642,7 +679,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
continue;
}
const CScript& prevPubKey = coin.out.scriptPubKey;
const CAmount& amount = coin.out.nValue;
const CConfidentialValue& amount = coin.out.nValue;
SignatureData sigdata = DataFromTransaction(mergedTx, i, coin.out);
// Only sign SIGHASH_SINGLE if there's a corresponding output:
@ -812,6 +849,16 @@ static int CommandLineRawTx(int argc, char* argv[])
value = arg.substr(eqpos + 1);
}
std::string serialization = gArgs.GetArg("-serialization", "ELEMENTS");
if (serialization == "ELEMENTS") {
g_con_elementsmode = true;
} else if (serialization == "BITCOIN") {
g_con_elementsmode = false;
} else {
PrintExceptionContinue(nullptr, "Invalid serialization argument");
throw;
}
MutateTx(tx, key, value);
}

199
src/blech32.cpp Normal file
View file

@ -0,0 +1,199 @@
// Copyright (c) 2017 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <blech32.h>
/*
* IMPORTANT NOTE: Comments below may largely pertain for bech32, not blech32.
* Some of these magic constants have changes.
* See liquid_addr.py for compact difference from bech32
* TODO: Update comments
*/
namespace
{
typedef std::vector<uint8_t> data;
/** The Blech32 character set for encoding. */
const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
/** The Blech32 character set for decoding. */
const int8_t CHARSET_REV[128] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
};
/** Concatenate two byte arrays. */
data Cat(data x, const data& y)
{
x.insert(x.end(), y.begin(), y.end());
return x;
}
/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to
* make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher
* bits correspond to earlier values. */
uint32_t PolyMod(const data& v)
{
// The input is interpreted as a list of coefficients of a polynomial over F = GF(32), with an
// implicit 1 in front. If the input is [v0,v1,v2,v3,v4], that polynomial is v(x) =
// 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. The implicit 1 guarantees that
// [v0,v1,v2,...] has a distinct checksum from [0,v0,v1,v2,...].
// The output is a 30-bit integer whose 5-bit groups are the coefficients of the remainder of
// v(x) mod g(x), where g(x) is the Blech32 generator,
// x^6 + {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}. g(x) is chosen in such a way
// that the resulting code is a BCH code, guaranteeing detection of up to 3 errors within a
// window of 1023 characters. Among the various possible BCH codes, one was selected to in
// fact guarantee detection of up to 4 errors within a window of 89 characters.
// Note that the coefficients are elements of GF(32), here represented as decimal numbers
// between {}. In this finite field, addition is just XOR of the corresponding numbers. For
// example, {27} + {13} = {27 ^ 13} = {22}. Multiplication is more complicated, and requires
// treating the bits of values themselves as coefficients of a polynomial over a smaller field,
// GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, {5} * {26} =
// (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + a^3 + a) = a^6 + a^5 + a^4 + a
// = a^3 + 1 (mod a^5 + a^3 + 1) = {9}.
// During the course of the loop below, `c` contains the bitpacked coefficients of the
// polynomial constructed from just the values of v that were processed so far, mod g(x). In
// the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of
// v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value
// for `c`.
uint64_t c = 1;
for (const auto v_i : v) {
// We want to update `c` to correspond to a polynomial with one extra term. If the initial
// value of `c` consists of the coefficients of c(x) = f(x) mod g(x), we modify it to
// correspond to c'(x) = (f(x) * x + v_i) mod g(x), where v_i is the next input to
// process. Simplifying:
// c'(x) = (f(x) * x + v_i) mod g(x)
// ((f(x) mod g(x)) * x + v_i) mod g(x)
// (c(x) * x + v_i) mod g(x)
// If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to compute
// c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + v_i mod g(x)
// = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i mod g(x)
// = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i
// If we call (x^6 mod g(x)) = k(x), this can be written as
// c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i) + c0*k(x)
// First, determine the value of c0:
uint8_t c0 = c >> 55; // ELEMENTS: 25->55
// Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i:
c = ((c & 0x7fffffffffffff) << 5) ^ v_i; // ELEMENTS 0x1ffffff->0x7fffffffffffff
// Finally, for each set bit n in c0, conditionally add {2^n}k(x):
if (c0 & 1) c ^= 0x7d52fba40bd886; // ELEMENTS
if (c0 & 2) c ^= 0x5e8dbf1a03950c; // ELEMENTS
if (c0 & 4) c ^= 0x1c3a3c74072a18; // ELEMENTS
if (c0 & 8) c ^= 0x385d72fa0e5139; // ELEMENTS
if (c0 & 16) c ^= 0x7093e5a608865b; // ELEMENTS
}
return c;
}
/** Convert to lower case. */
inline unsigned char LowerCase(unsigned char c)
{
return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c;
}
/** Expand a HRP for use in checksum computation. */
data ExpandHRP(const std::string& hrp)
{
data ret;
ret.reserve(hrp.size() + 90);
ret.resize(hrp.size() * 2 + 1);
for (size_t i = 0; i < hrp.size(); ++i) {
unsigned char c = hrp[i];
ret[i] = c >> 5;
ret[i + hrp.size() + 1] = c & 0x1f;
}
ret[hrp.size()] = 0;
return ret;
}
/** Verify a checksum. */
bool VerifyChecksum(const std::string& hrp, const data& values)
{
// PolyMod computes what value to xor into the final values to make the checksum 0. However,
// if we required that the checksum was 0, it would be the case that appending a 0 to a valid
// list of values would result in a new valid list. For that reason, Blech32 requires the
// resulting checksum to be 1 instead.
return PolyMod(Cat(ExpandHRP(hrp), values)) == 1;
}
/** Create a checksum. */
data CreateChecksum(const std::string& hrp, const data& values)
{
data enc = Cat(ExpandHRP(hrp), values);
enc.resize(enc.size() + 12); // ELEMENTS: Append 6->12 zeroes
uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes.
data ret(12); // ELEMENTS: 6->12
for (size_t i = 0; i < 12; ++i) { // ELEMENTS: 6->12
// Convert the 5-bit groups in mod to checksum values.
ret[i] = (mod >> (5 * (11 - i))) & 31; // ELEMENTS: 5->11
}
return ret;
}
} // namespace
namespace blech32
{
/** Encode a Blech32 string. */
std::string Encode(const std::string& hrp, const data& values) {
data checksum = CreateChecksum(hrp, values);
data combined = Cat(values, checksum);
std::string ret = hrp + '1';
ret.reserve(ret.size() + combined.size());
for (const auto c : combined) {
ret += CHARSET[c];
}
return ret;
}
/** Decode a Blech32 string. */
std::pair<std::string, data> Decode(const std::string& str) {
bool lower = false, upper = false;
for (size_t i = 0; i < str.size(); ++i) {
unsigned char c = str[i];
if (c >= 'a' && c <= 'z') lower = true;
else if (c >= 'A' && c <= 'Z') upper = true;
else if (c < 33 || c > 126) return {};
}
if (lower && upper) return {};
size_t pos = str.rfind('1');
if (str.size() > 1000 || pos == str.npos || pos == 0 || pos + 13 > str.size()) { // ELEMENTS: 90->1000, 7->13
return {};
}
data values(str.size() - 1 - pos);
for (size_t i = 0; i < str.size() - 1 - pos; ++i) {
unsigned char c = str[i + pos + 1];
int8_t rev = CHARSET_REV[c];
if (rev == -1) {
return {};
}
values[i] = rev;
}
std::string hrp;
for (size_t i = 0; i < pos; ++i) {
hrp += LowerCase(str[i]);
}
if (!VerifyChecksum(hrp, values)) {
return {};
}
return {hrp, data(values.begin(), values.end() - 12)};
}
} // namespace blech32

30
src/blech32.h Normal file
View file

@ -0,0 +1,30 @@
// Copyright (c) 2017 Pieter Wuille
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// Bech32 is a string encoding format used in newer address types.
// The output consists of a human-readable part (alphanumeric), a
// separator character (1), and a base32 data section, the last
// 6 characters of which are a checksum.
//
// For more information, see BIP 173.
#ifndef BITCOIN_BLECH32_H
#define BITCOIN_BLECH32_H
#include <stdint.h>
#include <string>
#include <vector>
namespace blech32
{
/** Encode a Bech32 string. Returns the empty string in case of failure. */
std::string Encode(const std::string& hrp, const std::vector<uint8_t>& values);
/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */
std::pair<std::string, std::vector<uint8_t>> Decode(const std::string& str);
} // namespace blech32
#endif // BITCOIN_BLECH32_H

579
src/blind.cpp Normal file
View file

@ -0,0 +1,579 @@
// Copyright (c) 2017-2019 The Elements Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <blind.h>
#include <hash.h>
#include <primitives/transaction.h>
#include <primitives/confidential.h>
#include <issuance.h>
#include <random.h>
#include <util.h>
static secp256k1_context* secp256k1_blind_context = NULL;
class Blind_ECC_Init {
public:
Blind_ECC_Init() {
assert(secp256k1_blind_context == NULL);
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
assert(ctx != NULL);
secp256k1_blind_context = ctx;
}
~Blind_ECC_Init() {
secp256k1_context *ctx = secp256k1_blind_context;
secp256k1_blind_context = NULL;
if (ctx) {
secp256k1_context_destroy(ctx);
}
}
};
static Blind_ECC_Init ecc_init_on_load;
bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector<unsigned char>& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out)
{
if (!blinding_key.IsValid() || vchRangeproof.size() == 0) {
return false;
}
CPubKey ephemeral_key(nonce_commitment.vchCommitment);
if (nonce_commitment.vchCommitment.size() > 0 && !ephemeral_key.IsFullyValid()) {
return false;
}
// ECDH or not depending on if nonce commitment is non-empty
uint256 nonce;
bool blank_nonce = false;
if (nonce_commitment.vchCommitment.size() > 0) {
nonce = blinding_key.ECDH(ephemeral_key);
CSHA256().Write(nonce.begin(), 32).Finalize(nonce.begin());
} else {
// Use blinding key directly, and don't commit to a scriptpubkey
// This is used for issuance inputs.
blank_nonce = true;
nonce = uint256(std::vector<unsigned char>(blinding_key.begin(), blinding_key.end()));
}
// API-prescribed sidechannel maximum size, though we only use 64 bytes
unsigned char msg[4096] = {0};
// 32 bytes of asset type, 32 bytes of asset blinding factor in sidechannel
size_t msg_size = 64;
// If value is unblinded, we don't support unblinding just the asset
if (!conf_value.IsCommitment()) {
return false;
}
// Valid asset commitment?
secp256k1_generator observed_gen;
if (conf_asset.IsCommitment()) {
if (secp256k1_generator_parse(secp256k1_blind_context, &observed_gen, &conf_asset.vchCommitment[0]) != 1)
return false;
} else if (conf_asset.IsExplicit()) {
if (secp256k1_generator_generate(secp256k1_blind_context, &observed_gen, conf_asset.GetAsset().begin()) != 1)
return false;
}
// Valid value commitment?
secp256k1_pedersen_commitment value_commit;
if (secp256k1_pedersen_commitment_parse(secp256k1_blind_context, &value_commit, conf_value.vchCommitment.data()) != 1) {
return false;
}
// Rewind rangeproof
uint64_t min_value, max_value, amount;
if (!secp256k1_rangeproof_rewind(secp256k1_blind_context, blinding_factor_out.begin(), &amount, msg, &msg_size, nonce.begin(), &min_value, &max_value, &value_commit, &vchRangeproof[0], vchRangeproof.size(), (committedScript.size() && !blank_nonce)? &committedScript.front(): NULL, blank_nonce ? 0 : committedScript.size(), &observed_gen)) {
return false;
}
// Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check)
if (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)) {
return false;
}
// Convenience pointers to starting point of each recovered 32 byte message
unsigned char *asset_type = msg;
unsigned char *asset_blinder = msg+32;
// Asset sidechannel of asset type + asset blinder
secp256k1_generator recalculated_gen;
if (msg_size != 64 || secp256k1_generator_generate_blinded(secp256k1_blind_context, &recalculated_gen, asset_type, asset_blinder) != 1) {
return false;
}
// Serialize both generators then compare
unsigned char observed_generator[33];
unsigned char derived_generator[33];
secp256k1_generator_serialize(secp256k1_blind_context, observed_generator, &observed_gen);
secp256k1_generator_serialize(secp256k1_blind_context, derived_generator, &recalculated_gen);
if (memcmp(observed_generator, derived_generator, sizeof(observed_generator))) {
return false;
}
amount_out = (CAmount)amount;
asset_out = CAsset(std::vector<unsigned char>(asset_type, asset_type+32));
asset_blinding_factor_out = uint256(std::vector<unsigned char>(asset_blinder, asset_blinder+32));
return true;
}
// Create surjection proof
bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector<secp256k1_fixed_asset_tag>& surjection_targets, const std::vector<secp256k1_generator>& target_asset_generators, const std::vector<uint256 >& target_asset_blinders, const std::vector<const unsigned char*> asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset)
{
int ret;
// 1 to 3 targets
size_t nInputsToSelect = std::min((size_t)3, surjection_targets.size());
unsigned char randseed[32];
GetStrongRandBytes(randseed, 32);
size_t input_index;
secp256k1_surjectionproof proof;
secp256k1_fixed_asset_tag tag;
memcpy(&tag, asset.begin(), 32);
// Find correlation between asset tag and listed input tags
if (secp256k1_surjectionproof_initialize(secp256k1_blind_context, &proof, &input_index, &surjection_targets[0], surjection_targets.size(), nInputsToSelect, &tag, 100, randseed) == 0) {
return false;
}
// Using the input chosen, build proof
ret = secp256k1_surjectionproof_generate(secp256k1_blind_context, &proof, target_asset_generators.data(), target_asset_generators.size(), &output_asset_gen, input_index, target_asset_blinders[input_index].begin(), asset_blindptrs[asset_blindptrs.size()-1]);
assert(ret == 1);
// Double-check answer
ret = secp256k1_surjectionproof_verify(secp256k1_blind_context, &proof, target_asset_generators.data(), target_asset_generators.size(), &output_asset_gen);
assert(ret != 0);
// Serialize into output witness structure
size_t output_len = secp256k1_surjectionproof_serialized_size(secp256k1_blind_context, &proof);
txoutwit.vchSurjectionproof.resize(output_len);
secp256k1_surjectionproof_serialize(secp256k1_blind_context, &txoutwit.vchSurjectionproof[0], &output_len, &proof);
assert(output_len == txoutwit.vchSurjectionproof.size());
return true;
}
// Creates ECDH nonce commitment using ephemeral key and output_pubkey
uint256 GenerateOutputRangeproofNonce(CTxOut& out, const CPubKey output_pubkey)
{
// Generate ephemeral key for ECDH nonce generation
CKey ephemeral_key;
ephemeral_key.MakeNewKey(true);
CPubKey ephemeral_pubkey = ephemeral_key.GetPubKey();
assert(ephemeral_pubkey.size() == CConfidentialNonce::nCommittedSize);
out.nNonce.vchCommitment.resize(ephemeral_pubkey.size());
memcpy(&out.nNonce.vchCommitment[0], &ephemeral_pubkey[0], ephemeral_pubkey.size());
// Generate nonce
uint256 nonce = ephemeral_key.ECDH(output_pubkey);
CSHA256().Write(nonce.begin(), 32).Finalize(nonce.begin());
return nonce;
}
bool GenerateRangeproof(std::vector<unsigned char>& rangeproof, const std::vector<unsigned char*>& value_blindptrs, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, std::vector<const unsigned char*>& asset_blindptrs)
{
// Prep range proof
size_t nRangeProofLen = 5134;
rangeproof.resize(nRangeProofLen);
// Compose sidechannel message to convey asset info (ID and asset blinds)
unsigned char asset_message[64];
memcpy(asset_message, asset.begin(), 32);
memcpy(asset_message+32, asset_blindptrs[asset_blindptrs.size()-1], 32);
// Sign rangeproof
// If min_value is 0, scriptPubKey must be unspendable
int res = secp256k1_rangeproof_sign(secp256k1_blind_context, rangeproof.data(), &nRangeProofLen, scriptPubKey.IsUnspendable() ? 0 : 1, &value_commit, value_blindptrs.back(), nonce.begin(), std::min(std::max((int)gArgs.GetArg("-ct_exponent", 0), -1),18), std::min(std::max((int)gArgs.GetArg("-ct_bits", 36), 1), 51), amount, asset_message, sizeof(asset_message), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &gen);
rangeproof.resize(nRangeProofLen);
return (res == 1);
}
void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr)
{
conf_asset.vchCommitment.resize(CConfidentialAsset::nCommittedSize);
int ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &asset_gen, asset.begin(), asset_blindptr);
assert(ret == 1);
ret = secp256k1_generator_serialize(secp256k1_blind_context, conf_asset.vchCommitment.data(), &asset_gen);
assert(ret != 0);
}
void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount)
{
int ret;
conf_value.vchCommitment.resize(CConfidentialValue::nCommittedSize);
ret = secp256k1_pedersen_commit(secp256k1_blind_context, &value_commit, value_blindptr, amount, &asset_gen);
assert(ret != 0);
secp256k1_pedersen_commitment_serialize(secp256k1_blind_context, conf_value.vchCommitment.data(), &value_commit);
assert(conf_value.IsValid());
}
size_t GetNumIssuances(const CTransaction& tx)
{
unsigned int num_issuances = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++) {
if (!tx.vin[i].assetIssuance.IsNull()) {
if (!tx.vin[i].assetIssuance.nAmount.IsNull()) {
num_issuances++;
}
if (!tx.vin[i].assetIssuance.nInflationKeys.IsNull()) {
num_issuances++;
}
}
}
return num_issuances;
}
int BlindTransaction(std::vector<uint256 >& input_value_blinding_factors, const std::vector<uint256 >& input_asset_blinding_factors, const std::vector<CAsset >& input_assets, const std::vector<CAmount >& input_amounts, std::vector<uint256 >& out_val_blind_factors, std::vector<uint256 >& out_asset_blind_factors, const std::vector<CPubKey>& output_pubkeys, const std::vector<CKey>& issuance_blinding_privkey, const std::vector<CKey>& token_blinding_privkey, CMutableTransaction& tx, std::vector<std::vector<unsigned char> >* auxiliary_generators)
{
// Sanity check input data and output_pubkey size, clear other output data
assert(tx.vout.size() >= output_pubkeys.size());
assert(tx.vin.size()+GetNumIssuances(tx) >= issuance_blinding_privkey.size());
assert(tx.vin.size()+GetNumIssuances(tx) >= token_blinding_privkey.size());
out_val_blind_factors.clear();
out_val_blind_factors.resize(tx.vout.size());
out_asset_blind_factors.clear();
out_asset_blind_factors.resize(tx.vout.size());
assert(tx.vin.size() == input_value_blinding_factors.size());
assert(tx.vin.size() == input_asset_blinding_factors.size());
assert(tx.vin.size() == input_assets.size());
assert(tx.vin.size() == input_amounts.size());
if (auxiliary_generators) {
assert(auxiliary_generators->size() >= tx.vin.size());
}
std::vector<unsigned char*> value_blindptrs;
std::vector<const unsigned char*> asset_blindptrs;
std::vector<uint64_t> blinded_amounts;
value_blindptrs.reserve(tx.vout.size() + tx.vin.size());
asset_blindptrs.reserve(tx.vout.size() + tx.vin.size());
int ret;
int num_blind_attempts = 0, num_issuance_blind_attempts = 0, num_blinded = 0;
//Surjection proof prep
// Needed to surj init, only matches to output asset matters, rest can be garbage
std::vector<secp256k1_fixed_asset_tag> surjection_targets;
// Needed to construct the proof itself. Generators must match final transaction to be valid
std::vector<secp256k1_generator> target_asset_generators;
surjection_targets.resize(tx.vin.size()*3);
target_asset_generators.resize(tx.vin.size()*3);
// input_asset_blinding_factors is only for inputs, not for issuances(0 by def)
// but we need to create surjection proofs against this list so we copy and insert 0's
// where issuances occur.
std::vector<uint256> target_asset_blinders;
size_t totalTargets = 0;
for (size_t i = 0; i < tx.vin.size(); i++) {
// For each input we either need the asset/blinds or the generator
if (input_assets[i].IsNull()) {
// If non-empty generator exists, parse
if (auxiliary_generators) {
// Parse generator here
ret = secp256k1_generator_parse(secp256k1_blind_context, &target_asset_generators[totalTargets], &(*auxiliary_generators)[i][0]);
if (ret != 1) {
return -1;
}
} else {
return -1;
}
} else {
ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &target_asset_generators[totalTargets], input_assets[i].begin(), input_asset_blinding_factors[i].begin());
assert(ret == 1);
}
memcpy(&surjection_targets[totalTargets], input_assets[i].begin(), 32);
target_asset_blinders.push_back(input_asset_blinding_factors[i]);
totalTargets++;
// Create target generators for issuances
CAssetIssuance& issuance = tx.vin[i].assetIssuance;
uint256 entropy;
CAsset asset;
CAsset token;
if (!issuance.IsNull()) {
if (issuance.nAmount.IsCommitment() || issuance.nInflationKeys.IsCommitment()) {
return -1;
}
// New Issuance
if (issuance.assetBlindingNonce.IsNull()) {
bool blind_issuance = (token_blinding_privkey.size() > i && token_blinding_privkey[i].IsValid()) ? true : false;
GenerateAssetEntropy(entropy, tx.vin[i].prevout, issuance.assetEntropy);
CalculateAsset(asset, entropy);
CalculateReissuanceToken(token, entropy, blind_issuance);
} else {
CalculateAsset(asset, issuance.assetEntropy);
}
if (!issuance.nAmount.IsNull()) {
memcpy(&surjection_targets[totalTargets], asset.begin(), 32);
ret = secp256k1_generator_generate(secp256k1_blind_context, &target_asset_generators[totalTargets], asset.begin());
assert(ret != 0);
// Issuance asset cannot be blinded by definition
target_asset_blinders.push_back(uint256());
totalTargets++;
}
if (!issuance.nInflationKeys.IsNull()) {
assert(!token.IsNull());
memcpy(&surjection_targets[totalTargets], token.begin(), 32);
ret = secp256k1_generator_generate(secp256k1_blind_context, &target_asset_generators[totalTargets], token.begin());
assert(ret != 0);
// Issuance asset cannot be blinded by definition
target_asset_blinders.push_back(uint256());
totalTargets++;
}
}
}
if (auxiliary_generators) {
// Process any additional targets from auxiliary_generators
// we know nothing about it other than the generator itself
for (size_t i = tx.vin.size(); i < auxiliary_generators->size(); i++) {
ret = secp256k1_generator_parse(secp256k1_blind_context, &target_asset_generators[totalTargets], &(*auxiliary_generators)[i][0]);
if (ret != 1) {
return -1;
}
memset(&surjection_targets[totalTargets], 0, 32);
target_asset_blinders.push_back(uint256());
totalTargets++;
}
}
// Resize the target surjection lists to how many actually exist
assert(totalTargets == target_asset_blinders.size());
surjection_targets.resize(totalTargets);
target_asset_generators.resize(totalTargets);
//Total blinded inputs that you own (that you are balancing against)
int num_known_input_blinds = 0;
//Number of outputs and issuances to blind
int num_to_blind = 0;
// Make sure witness lengths are correct
tx.witness.vtxoutwit.resize(tx.vout.size());
tx.witness.vtxinwit.resize(tx.vin.size());
size_t txoutwitsize = tx.witness.vtxoutwit.size();
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
if (!input_value_blinding_factors[nIn].IsNull() || !input_asset_blinding_factors[nIn].IsNull()) {
if (input_amounts[nIn] < 0) {
return -1;
}
value_blindptrs.push_back(input_value_blinding_factors[nIn].begin());
asset_blindptrs.push_back(input_asset_blinding_factors[nIn].begin());
blinded_amounts.push_back(input_amounts[nIn]);
num_known_input_blinds++;
}
// Count number of issuance pseudo-inputs to blind
CAssetIssuance& issuance = tx.vin[nIn].assetIssuance;
if (!issuance.IsNull()) {
// Marked for blinding
if (issuance_blinding_privkey.size() > nIn && issuance_blinding_privkey[nIn].IsValid()) {
if(issuance.nAmount.IsExplicit() && tx.witness.vtxinwit[nIn].vchIssuanceAmountRangeproof.empty()) {
num_to_blind++;
} else {
return -1;
}
}
if (token_blinding_privkey.size() > nIn && token_blinding_privkey[nIn].IsValid()) {
if(issuance.nInflationKeys.IsExplicit() && tx.witness.vtxinwit[nIn].vchInflationKeysRangeproof.empty()) {
num_to_blind++;
} else {
return -1;
}
}
}
}
for (size_t nOut = 0; nOut < output_pubkeys.size(); nOut++) {
if (output_pubkeys[nOut].IsValid()) {
// Keys must be valid and outputs completely unblinded or else call fails
if (!output_pubkeys[nOut].IsFullyValid() ||
(!tx.vout[nOut].nValue.IsExplicit() || !tx.vout[nOut].nAsset.IsExplicit()) ||
(txoutwitsize > nOut && !tx.witness.vtxoutwit[nOut].IsNull())
|| tx.vout[nOut].IsFee()) {
return -1;
}
num_to_blind++;
}
}
//Running total of newly blinded outputs
static const unsigned char diff_zero[32] = {0};
assert(num_to_blind <= 10000); // More than 10k outputs? Stop spamming.
unsigned char blind[10000][32];
unsigned char asset_blind[10000][32];
secp256k1_pedersen_commitment value_commit;
secp256k1_generator asset_gen;
CAsset asset;
// First blind issuance pseudo-inputs
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
for (size_t nPseudo = 0; nPseudo < 2; nPseudo++) {
if ((nPseudo == 0 && issuance_blinding_privkey.size() > nIn && issuance_blinding_privkey[nIn].IsValid()) ||
(nPseudo == 1 && token_blinding_privkey.size() > nIn && token_blinding_privkey[nIn].IsValid())) {
num_blind_attempts++;
num_issuance_blind_attempts++;
CAssetIssuance& issuance = tx.vin[nIn].assetIssuance;
// First iteration does issuance asset, second inflation keys
CConfidentialValue& conf_value = nPseudo ? issuance.nInflationKeys : issuance.nAmount;
if (conf_value.IsNull()) {
continue;
}
CAmount amount = conf_value.GetAmount();
blinded_amounts.push_back(amount);
// Derive the asset of the issuance asset/token
if (issuance.assetBlindingNonce.IsNull()) {
uint256 entropy;
GenerateAssetEntropy(entropy, tx.vin[nIn].prevout, issuance.assetEntropy);
if (nPseudo == 0) {
CalculateAsset(asset, entropy);
} else {
bool blind_issuance = (token_blinding_privkey.size() > nIn && token_blinding_privkey[nIn].IsValid()) ? true : false;
CalculateReissuanceToken(asset, entropy, blind_issuance);
}
} else {
if (nPseudo == 0) {
CalculateAsset(asset, issuance.assetEntropy);
} else {
// Re-issuance only has one pseudo-input maximum
continue;
}
}
// Fill out the value blinders and blank asset blinder
GetStrongRandBytes(&blind[num_blind_attempts-1][0], 32);
// Issuances are not asset-blinded
memset(&asset_blind[num_blind_attempts-1][0], 0, 32);
value_blindptrs.push_back(&blind[num_blind_attempts-1][0]);
asset_blindptrs.push_back(&asset_blind[num_blind_attempts-1][0]);
if (num_blind_attempts == num_to_blind) {
// All outputs we own are unblinded, we don't support this type of blinding
// though it is possible. No privacy gained here, incompatible with secp api
return num_blinded;
}
if (tx.witness.vtxinwit.size() <= nIn) {
tx.witness.vtxinwit.resize(tx.vin.size());
}
CTxInWitness& txinwit = tx.witness.vtxinwit[nIn];
// Create unblinded generator. We throw away all but `asset_gen`
CConfidentialAsset conf_asset;
BlindAsset(conf_asset, asset_gen, asset, asset_blindptrs.back());
// Create value commitment
CreateValueCommitment(conf_value, value_commit, value_blindptrs.back(), asset_gen, amount);
// nonce should just be blinding key
uint256 nonce = nPseudo ? uint256(std::vector<unsigned char>(token_blinding_privkey[nIn].begin(), token_blinding_privkey[nIn].end())) : uint256(std::vector<unsigned char>(issuance_blinding_privkey[nIn].begin(), issuance_blinding_privkey[nIn].end()));
// Generate rangeproof, no script committed for issuances
bool rangeresult = GenerateRangeproof((nPseudo ? txinwit.vchInflationKeysRangeproof : txinwit.vchIssuanceAmountRangeproof), value_blindptrs, nonce, amount, CScript(), value_commit, asset_gen, asset, asset_blindptrs);
assert(rangeresult);
// Successfully blinded this issuance
num_blinded++;
}
}
}
// This section of code *only* deals with unblinded outputs
// that we want to blind
for (size_t nOut = 0; nOut < output_pubkeys.size(); nOut++) {
if (output_pubkeys[nOut].IsFullyValid()) {
CTxOut& out = tx.vout[nOut];
num_blind_attempts++;
CConfidentialAsset& conf_asset = out.nAsset;
CConfidentialValue& conf_value = out.nValue;
CAmount amount = conf_value.GetAmount();
asset = out.nAsset.GetAsset();
blinded_amounts.push_back(conf_value.GetAmount());
GetStrongRandBytes(&blind[num_blind_attempts-1][0], 32);
GetStrongRandBytes(&asset_blind[num_blind_attempts-1][0], 32);
value_blindptrs.push_back(&blind[num_blind_attempts-1][0]);
asset_blindptrs.push_back(&asset_blind[num_blind_attempts-1][0]);
// Last blinding factor r' is set as -(output's (vr + r') - input's (vr + r')).
// Before modifying the transaction or return arguments we must
// ensure the final blinding factor to not be its corresponding -vr (aka unblinded),
// or 0, in the case of 0-value output, insisting on additional output to blind.
if (num_blind_attempts == num_to_blind) {
// Can't successfully blind in this case, since -vr = r
// This check is assuming blinds are generated randomly
// Adversary would need to create all input blinds
// therefore would already know all your summed output amount anyways.
if (num_blind_attempts == 1 && num_known_input_blinds == 0) {
return num_blinded;
}
// Generate value we intend to insert
ret = secp256k1_pedersen_blind_generator_blind_sum(secp256k1_blind_context, &blinded_amounts[0], &asset_blindptrs[0], &value_blindptrs[0], num_blind_attempts + num_known_input_blinds, num_issuance_blind_attempts + num_known_input_blinds);
assert(ret);
// Resulting blinding factor can sometimes be 0
// where inputs are the negations of each other
// and the unblinded value of the output is 0.
// e.g. 1 unblinded input to 2 blinded outputs,
// then spent to 1 unblinded output. (vr + r')
// becomes just (r'), if this is 0, we can just
// abort and not blind and the math adds up.
// Count as success(to signal caller that nothing wrong) and return early
if (memcmp(diff_zero, &blind[num_blind_attempts-1][0], 32) == 0) {
return ++num_blinded;
}
}
CTxOutWitness& txoutwit = tx.witness.vtxoutwit[nOut];
out_val_blind_factors[nOut] = uint256(std::vector<unsigned char>(value_blindptrs[value_blindptrs.size()-1], value_blindptrs[value_blindptrs.size()-1]+32));
out_asset_blind_factors[nOut] = uint256(std::vector<unsigned char>(asset_blindptrs[asset_blindptrs.size()-1], asset_blindptrs[asset_blindptrs.size()-1]+32));
//Blind the asset ID
BlindAsset(conf_asset, asset_gen, asset, asset_blindptrs.back());
// Create value commitment
CreateValueCommitment(conf_value, value_commit, value_blindptrs.back(), asset_gen, amount);
// Generate nonce for rewind by owner
uint256 nonce = GenerateOutputRangeproofNonce(out, output_pubkeys[nOut]);
// Generate rangeproof
bool rangeresult = GenerateRangeproof(txoutwit.vchRangeproof, value_blindptrs, nonce, amount, out.scriptPubKey, value_commit, asset_gen, asset, asset_blindptrs);
assert(rangeresult);
// Create surjection proof for this output
if (!SurjectOutput(txoutwit, surjection_targets, target_asset_generators, target_asset_blinders, asset_blindptrs, asset_gen, asset)) {
continue;
}
// Successfully blinded this output
num_blinded++;
}
}
return num_blinded;
}
void RawFillBlinds(CMutableTransaction& tx, std::vector<uint256>& output_value_blinds, std::vector<uint256>& output_asset_blinds, std::vector<CPubKey>& output_pubkeys) {
for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) {
// Any place-holder blinding pubkeys are extracted
if (tx.vout[nOut].nValue.IsExplicit()) {
CPubKey pubkey(tx.vout[nOut].nNonce.vchCommitment);
if (pubkey.IsFullyValid()) {
output_pubkeys.push_back(pubkey);
} else {
output_pubkeys.push_back(CPubKey());
}
}
// No way to unblind anything, just fill out
output_value_blinds.push_back(uint256());
output_asset_blinds.push_back(uint256());
}
// We cannot unwind issuance inputs because there is no nonce placeholder for pubkeys
}

63
src/blind.h Normal file
View file

@ -0,0 +1,63 @@
// Copyright (c) 2017-2019 The Elements Core developers
// // Distributed under the MIT software license, see the accompanying
// // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_BLIND_H
#define BITCOIN_BLIND_H
#include <key.h>
#include <pubkey.h>
#include <primitives/transaction.h>
#include <primitives/confidential.h>
#include <secp256k1.h>
#include <secp256k1_rangeproof.h>
#include <secp256k1_surjectionproof.h>
//! ELEMENTS: 36-bit rangeproof size
static const size_t DEFAULT_RANGEPROOF_SIZE = 2893;
/*
* Unblind a pair of confidential asset and value.
* Note that unblinded data will only be outputted if *BOTH* asset and value could be unblinded.
*
* blinding_key is used to create the nonce to rewind the rangeproof in conjunction with the nNonce commitment. In the case of a 0-length nNonce, the blinding key is directly used as the nonce.
* Currently there is only a sidechannel message in the rangeproof so a valid rangeproof must
* be included in the pair to recover value and asset data.
*/
bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector<unsigned char>& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out);
bool GenerateRangeproof(std::vector<unsigned char>& rangeproof, const std::vector<unsigned char*>& value_blindptrs, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, std::vector<const unsigned char*>& asset_blindptrs);
bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector<secp256k1_fixed_asset_tag>& surjection_targets, const std::vector<secp256k1_generator>& target_asset_generators, const std::vector<uint256 >& target_asset_blinders, const std::vector<const unsigned char*> asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset);
uint256 GenerateOutputRangeproofNonce(CTxOut& out, const CPubKey output_pubkey);
void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr);
void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount);
/* Returns the number of outputs that were successfully blinded.
* In many cases a `0` can be fixed by adding an additional output.
* @param[in] input_blinding_factors - A vector of input blinding factors that will be used to create the balanced output blinding factors
* @param[in] input_asset_blinding_factors - A vector of input asset blinding factors that will be used to create the balanced output blinding factors
* @param[in] input_assets - the asset of each corresponding input
* @param[in] input_amounts - the unblinded amounts of each input. Required for owned blinded inputs
* @param[in/out] output_blinding_factors - A vector of blinding factors. New blinding factors will replace these values.
* @param[in/out] output_asset_blinding_factors - A vector of asset blinding factors. New blinding factors will replace these values.
* @param[in] output_pubkeys - If valid, corresponding output must be unblinded, and will result in fully blinded output, modifying the output blinding arguments as well.
* @param[in] vBlindIssuanceAsset - List of keys to use as nonces for issuance asset blinding.
* @param[in] vBlindIssuanceToken - List of keys to use as nonces for issuance token blinding.
* @param[in/out] tx - The transaction to be modified.
* @param[in] auxiliary_generators - a list of generators to create surjection proofs when inputs are not owned by caller. Passing in non-empty elements results in ignoring of other input arguments for that index
*/
int BlindTransaction(std::vector<uint256 >& input_value_blinding_factors, const std::vector<uint256 >& input_asset_blinding_factors, const std::vector<CAsset >& input_assets, const std::vector<CAmount >& input_amounts, std::vector<uint256 >& out_val_blind_factors, std::vector<uint256 >& out_asset_blind_factors, const std::vector<CPubKey>& output_pubkeys, const std::vector<CKey>& issuance_blinding_privkey, const std::vector<CKey>& token_blinding_privkey, CMutableTransaction& tx, std::vector<std::vector<unsigned char> >* auxiliary_generators = nullptr);
/*
* Extract pubkeys from nonce commitment placeholders, fill out vector of blank output blinding data
*/
void RawFillBlinds(CMutableTransaction& tx, std::vector<uint256>& output_value_blinds, std::vector<uint256>& output_asset_blinds, std::vector<CPubKey>& output_pubkeys);
size_t GetNumIssuances(const CTransaction& tx);
#endif //BITCOIN_BLIND_H

View file

@ -7,6 +7,7 @@
#include <chainparamsseeds.h>
#include <consensus/merkle.h>
#include <issuance.h>
#include <primitives/transaction.h>
#include <tinyformat.h>
#include <util.h>
@ -47,10 +48,8 @@ static CBlock CreateGenesisBlock(const Consensus::Params& params, const CScript&
CMutableTransaction txNew;
txNew.nVersion = 1;
txNew.vin.resize(1);
txNew.vout.resize(1);
txNew.vin[0].scriptSig = genesisScriptSig;
txNew.vout[0].nValue = genesisReward;
txNew.vout[0].scriptPubKey = genesisOutputScript;
txNew.vout.push_back(CTxOut(CAsset(), genesisReward, genesisOutputScript));
CBlock genesis;
genesis.nTime = nTime;
@ -85,23 +84,6 @@ static CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint32_t nBits
return CreateGenesisBlock(params, genesisScriptSig, genesisOutputScript, nTime, nNonce, nBits, nVersion, genesisReward);
}
/** Add an issuance transaction to the genesis block. Typically used to pre-issue
* the policyAsset of a blockchain. The genesis block is not actually validated,
* so this transaction simply has to match issuance structure. */
static void AppendInitialIssuance(CBlock& genesis_block, const COutPoint& prevout, const int64_t asset_values, const CScript& issuance_destination) {
// Note: Genesis block isn't actually validated, outputs are entered into utxo db only
CMutableTransaction txNew;
txNew.nVersion = 1;
txNew.vin.resize(1);
txNew.vin[0].prevout = prevout;
txNew.vout.push_back(CTxOut(asset_values, issuance_destination));
genesis_block.vtx.push_back(MakeTransactionRef(std::move(txNew)));
genesis_block.hashMerkleRoot = BlockMerkleRoot(genesis_block);
}
/**
* Main network
*/
@ -144,11 +126,14 @@ public:
consensus.genesis_subsidy = 50*COIN;
consensus.connect_genesis_outputs = false;
consensus.subsidy_asset = CAsset();
anyonecanspend_aremine = false;
enforce_pak = false;
multi_data_permitted = false;
consensus.has_parent_chain = false;
g_signed_blocks = false;
g_con_elementsmode = false;
g_con_blockheightinheader = false;
/**
* The message start string is designed to be unlikely to occur in normal data.
@ -187,6 +172,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4};
bech32_hrp = "bc";
blech32_hrp = blech32_hrp;
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));
@ -266,10 +252,14 @@ public:
consensus.genesis_subsidy = 50*COIN;
consensus.connect_genesis_outputs = false;
consensus.subsidy_asset = CAsset();
anyonecanspend_aremine = false;
enforce_pak = false;
multi_data_permitted = false;
consensus.has_parent_chain = false;
g_signed_blocks = false;
g_con_elementsmode = false;
g_con_blockheightinheader = false;
pchMessageStart[0] = 0x0b;
pchMessageStart[1] = 0x11;
@ -298,6 +288,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "tb";
blech32_hrp = blech32_hrp;
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));
@ -362,10 +353,14 @@ public:
consensus.genesis_subsidy = 50*COIN;
consensus.connect_genesis_outputs = false;
consensus.subsidy_asset = CAsset();
anyonecanspend_aremine = false;
enforce_pak = false;
multi_data_permitted = false;
consensus.has_parent_chain = false;
g_signed_blocks = false;
g_con_elementsmode = false;
g_con_blockheightinheader = false;
pchMessageStart[0] = 0xfa;
pchMessageStart[1] = 0xbf;
@ -407,6 +402,7 @@ public:
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
bech32_hrp = "bcrt";
blech32_hrp = blech32_hrp;
/* enable fallback fee on regtest */
m_fallback_fee_enabled = true;
@ -486,6 +482,7 @@ class CCustomParams : public CRegTestParams {
m_fallback_fee_enabled = args.GetBoolArg("-fallback_fee_enabled", m_fallback_fee_enabled);
bech32_hrp = args.GetArg("-bech32_hrp", bech32_hrp);
blech32_hrp = args.GetArg("-blech32_hrp", "el");
base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-pubkeyprefix", 111));
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-scriptprefix", 196));
base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1, args.GetArg("-secretprefix", 239));
@ -527,7 +524,7 @@ class CCustomParams : public CRegTestParams {
// Note: These globals are needed to avoid circular dependencies.
// Default to true for custom chains.
g_con_blockheightinheader = args.GetBoolArg("-con_blockheightinheader", true);
g_con_elementswitness = args.GetBoolArg("-con_elementswitness", true);
g_con_elementsmode = args.GetBoolArg("-con_elementsmode", true);
// No subsidy for custom chains by default
consensus.genesis_subsidy = args.GetArg("-con_blocksubsidy", 0);
@ -565,6 +562,30 @@ class CCustomParams : public CRegTestParams {
base58Prefixes[PARENT_PUBKEY_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-parentpubkeyprefix", 111));
base58Prefixes[PARENT_SCRIPT_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-parentscriptprefix", 196));
parent_bech32_hrp = args.GetArg("-parent_bech32_hrp", "bcrt");
parent_blech32_hrp = args.GetArg("-parent_bech32_hrp", "bcrt");
base58Prefixes[BLINDED_ADDRESS] = std::vector<unsigned char>(1, args.GetArg("-blindedprefix", 4));
// Calculate pegged Bitcoin asset
std::vector<unsigned char> commit = CommitToArguments(consensus, strNetworkID);
uint256 entropy;
GenerateAssetEntropy(entropy, COutPoint(uint256(commit), 0), parentGenesisBlockHash);
// Elements serialization uses derivation, bitcoin serialization uses 0x00
if (g_con_elementsmode) {
CalculateAsset(consensus.pegged_asset, entropy);
} else {
assert(consensus.pegged_asset == CAsset());
}
consensus.parent_pegged_asset.SetHex(args.GetArg("-con_parent_pegged_asset", "0x00"));
initial_reissuance_tokens = args.GetArg("-initialreissuancetokens", 0);
// Subsidy asset, like policyAsset, defaults to the pegged_asset
consensus.subsidy_asset = consensus.pegged_asset;
if (gArgs.IsArgSet("-subsidyasset")) {
consensus.subsidy_asset = CAsset(uint256S(gArgs.GetArg("-subsidyasset", "0x00")));
}
// END ELEMENTS fields
@ -583,8 +604,8 @@ class CCustomParams : public CRegTestParams {
// Intended compatibility with Liquid v1 and elements-0.14.1
std::vector<unsigned char> commit = CommitToArguments(consensus, strNetworkID);
genesis = CreateGenesisBlock(consensus, CScript(commit), CScript(OP_RETURN), 1296688602, 2, 0x207fffff, 1, 0);
if (initialFreeCoins != 0) {
AppendInitialIssuance(genesis, COutPoint(uint256(commit), 0), initialFreeCoins, CScript() << OP_TRUE);
if (initialFreeCoins != 0 || initial_reissuance_tokens != 0) {
AppendInitialIssuance(genesis, COutPoint(uint256(commit), 0), parentGenesisBlockHash, (initialFreeCoins > 0) ? 1 : 0, initialFreeCoins, (initial_reissuance_tokens > 0) ? 1 : 0, initial_reissuance_tokens, CScript() << OP_TRUE);
}
} else {
throw std::runtime_error(strprintf("Invalid -genesis_style (%s)", consensus.genesis_style));
@ -601,6 +622,145 @@ public:
}
};
/**
* Liquid v1
*/
class CLiquidV1Params : public CChainParams {
public:
CLiquidV1Params()
{
strNetworkID = "liquidv1";
consensus.nSubsidyHalvingInterval = 150;
consensus.BIP16Exception = uint256();
consensus.BIP34Height = 0;
consensus.BIP34Hash = uint256();
consensus.BIP65Height = 0;
consensus.BIP66Height = 0;
consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks;
consensus.nPowTargetSpacing = 60; // Minute block assumption
consensus.fPowAllowMinDifficultyBlocks = true;
consensus.fPowNoRetargeting = true;
consensus.nRuleChangeActivationThreshold = 108;
consensus.nMinerConfirmationWindow = 144;
consensus.nMinimumChainWork = uint256();
consensus.defaultAssumeValid = uint256();
nPruneAfterHeight = 1000;
fDefaultConsistencyChecks = false;
fRequireStandard = true;
fMineBlocksOnDemand = false;
m_fallback_fee_enabled = false; // TODO Will this break stuff?
bech32_hrp = "ex"; // ex(plicit)
blech32_hrp = "lq"; // l(i)q(uid)
parent_bech32_hrp = "bc";
parent_blech32_hrp = "bc"; // Doesn't exist but...
base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1, 57);
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1, 39);
base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1, 128);
base58Prefixes[BLINDED_ADDRESS]= std::vector<unsigned char>(1,12);
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E};
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4};
base58Prefixes[PARENT_PUBKEY_ADDRESS] = std::vector<unsigned char>(1,0);
base58Prefixes[PARENT_SCRIPT_ADDRESS] = std::vector<unsigned char>(1,5);
pchMessageStart[0] = 0xfa;
pchMessageStart[1] = 0xbf;
pchMessageStart[2] = 0xb5;
pchMessageStart[3] = 0xda;
nDefaultPort = 7042;
vSeeds.clear();
vSeeds.emplace_back("seed.liquidnetwork.io");
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_liquidv1, pnSeed6_liquidv1 + ARRAYLEN(pnSeed6_liquidv1));
//
// ELEMENTS fields
consensus.genesis_style = "elements"; // unused here but let's set it anyways
// Block signing encumberance script, default of 51 aka OP_TRUE
std::vector<unsigned char> sign_bytes = ParseHex("5b21026a2a106ec32c8a1e8052e5d02a7b0a150423dbd9b116fc48d46630ff6e6a05b92102791646a8b49c2740352b4495c118d876347bf47d0551c01c4332fdc2df526f1a2102888bda53a424466b0451627df22090143bbf7c060e9eacb1e38426f6b07f2ae12102aee8967150dee220f613de3b239320355a498808084a93eaf39a34dcd62024852102d46e9259d0a0bb2bcbc461a3e68f34adca27b8d08fbe985853992b4b104e27412102e9944e35e5750ab621e098145b8e6cf373c273b7c04747d1aa020be0af40ccd62102f9a9d4b10a6d6c56d8c955c547330c589bb45e774551d46d415e51cd9ad5116321033b421566c124dfde4db9defe4084b7aa4e7f36744758d92806b8f72c2e943309210353dcc6b4cf6ad28aceb7f7b2db92a4bf07ac42d357adf756f3eca790664314b621037f55980af0455e4fb55aad9b85a55068bb6dc4740ea87276dc693f4598db45fa210384001daa88dabd23db878dbb1ce5b4c2a5fa72c3113e3514bf602325d0c37b8e21039056d089f2fe72dbc0a14780b4635b0dc8a1b40b7a59106325dd1bc45cc70493210397ab8ea7b0bf85bc7fc56bb27bf85e75502e94e76a6781c409f3f2ec3d1122192103b00e3b5b77884bf3cae204c4b4eac003601da75f96982ffcb3dcb29c5ee419b92103c1f3c0874cfe34b8131af34699589aacec4093399739ae352e8a46f80a6f68375fae");
consensus.signblockscript = CScript(sign_bytes.begin(), sign_bytes.end());
consensus.max_block_signature_size = 12*74; // 11 signatures plus wiggle room
g_signed_blocks = true;
g_con_blockheightinheader = true;
g_con_elementsmode = true;
consensus.genesis_subsidy = 0;
// All non-zero coinbase outputs must go to this scriptPubKey
std::vector<unsigned char> man_bytes = ParseHex("76a914fc26751a5025129a2fd006c6fbfa598ddd67f7e188ac");
consensus.mandatory_coinbase_destination = CScript(man_bytes.begin(), man_bytes.end()); // Blank script allows any coinbase destination
// Custom chains connect coinbase outputs to db by default
consensus.connect_genesis_outputs = true;
initialFreeCoins = 0;
anyonecanspend_aremine = false;
consensus.has_parent_chain = true;
enforce_pak = true;
multi_data_permitted = true;
parentGenesisBlockHash = uint256S("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
const bool parent_genesis_is_null = parentGenesisBlockHash == uint256();
assert(consensus.has_parent_chain != parent_genesis_is_null);
consensus.parentChainPowLimit = uint256S("0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.parent_chain_signblockscript = CScript(); // It has PoW
consensus.pegin_min_depth = 100;
const CScript default_script(CScript() << OP_TRUE);
consensus.fedpegScript = StrHexToScriptWithDefault("745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae", default_script);
// Calculate pegged Bitcoin asset
std::vector<unsigned char> commit = CommitToArguments(consensus, strNetworkID);
uint256 entropy;
GenerateAssetEntropy(entropy, COutPoint(uint256(commit), 0), parentGenesisBlockHash);
// Elements serialization uses derivation, bitcoin serialization uses 0x00
if (g_con_elementsmode) {
CalculateAsset(consensus.pegged_asset, entropy);
} else {
assert(consensus.pegged_asset == CAsset());
}
consensus.parent_pegged_asset.SetHex("0x00"); // No parent pegged asset
initial_reissuance_tokens = 0;
consensus.subsidy_asset = consensus.pegged_asset;
// CSV always active
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0;
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
// Segwit, likewise
consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].bit = 1;
consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
// Finally, create genesis block
genesis = CreateGenesisBlock(consensus, CScript(commit), CScript(OP_RETURN), 1296688602, 2, 0x207fffff, 1, 0);
consensus.hashGenesisBlock = genesis.GetHash();
assert(consensus.hashGenesisBlock.GetHex() == "1466275836220db2944ca059a3a10ef6fd2ea684b0688d2c379296888a206003");
}
};
static std::unique_ptr<const CChainParams> globalChainParams;
const CChainParams &Params() {
@ -617,6 +777,8 @@ std::unique_ptr<const CChainParams> CreateChainParams(const std::string& chain)
return std::unique_ptr<CChainParams>(new CTestNetParams());
else if (chain == CBaseChainParams::REGTEST)
return std::unique_ptr<CChainParams>(new CRegTestParams(gArgs));
else if (chain == CBaseChainParams::LIQUID1)
return std::unique_ptr<CChainParams>(new CLiquidV1Params());
return std::unique_ptr<CChainParams>(new CCustomParams(chain, gArgs));
}

View file

@ -54,6 +54,7 @@ public:
EXT_PUBLIC_KEY,
EXT_SECRET_KEY,
// ELEMENTS
BLINDED_ADDRESS,
PARENT_PUBKEY_ADDRESS,
PARENT_SCRIPT_ADDRESS,
@ -80,6 +81,7 @@ public:
const std::vector<std::string>& DNSSeeds() const { return vSeeds; }
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
const std::string& Bech32HRP() const { return bech32_hrp; }
const std::string& Blech32HRP() const { return blech32_hrp; }
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
const CCheckpointData& Checkpoints() const { return checkpointData; }
const ChainTxData& TxData() const { return chainTxData; }
@ -87,6 +89,7 @@ public:
const uint256 ParentGenesisBlockHash() const { return parentGenesisBlockHash; }
bool anyonecanspend_aremine;
const std::string& ParentBech32HRP() const { return parent_bech32_hrp; }
const std::string& ParentBlech32HRP() const { return parent_blech32_hrp; }
bool GetEnforcePak() const { return enforce_pak; }
bool GetMultiDataPermitted() const { return multi_data_permitted; }
@ -100,9 +103,11 @@ protected:
std::vector<std::string> vSeeds;
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
std::string bech32_hrp;
std::string blech32_hrp;
std::string strNetworkID;
CBlock genesis;
CAmount initialFreeCoins;
CAmount initial_reissuance_tokens;
std::vector<SeedSpec6> vFixedSeeds;
bool fDefaultConsistencyChecks;
bool fRequireStandard;
@ -113,6 +118,7 @@ protected:
// ELEMENTS extra fields:
uint256 parentGenesisBlockHash;
std::string parent_bech32_hrp;
std::string parent_blech32_hrp;
bool enforce_pak;
bool multi_data_permitted;
};

View file

@ -14,6 +14,7 @@
const std::string CBaseChainParams::MAIN = "main";
const std::string CBaseChainParams::TESTNET = "test";
const std::string CBaseChainParams::REGTEST = "regtest";
const std::string CBaseChainParams::LIQUID1 = "liquidv1";
void SetupChainParamsBaseOptions()
{
@ -26,7 +27,7 @@ void SetupChainParamsBaseOptions()
gArgs.AddArg("-con_mandatorycoinbase", "All non-zero valued coinbase outputs must go to this scriptPubKey, if set.", false, OptionsCategory::ELEMENTS);
gArgs.AddArg("-con_blocksubsidy", "Defines the amount of block subsidy to start with, at genesis block.", false, OptionsCategory::ELEMENTS);
gArgs.AddArg("-con_connect_coinbase", "Connect outputs in genesis block to utxo database.", false, OptionsCategory::ELEMENTS);
gArgs.AddArg("-con_elementswitness", "Use Elements-like instead of Core-like witness encoding. This is required for CA/CT. (default: true)", false, OptionsCategory::ELEMENTS);
gArgs.AddArg("-con_elementsmode", "Use Elements-like instead of Core-like witness encoding. This is required for CA/CT. (default: true)", false, OptionsCategory::ELEMENTS);
gArgs.AddArg("-con_blockheightinheader", "Whether the chain includes the block height directly in the header, for easier validation of block height in low-resource environments. (default: true)", false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-con_genesis_style=<style>", "Use genesis style <style> (default: elements). Results in genesis block compatibility with various networks. Allowed values: elements, bitcoin", true, OptionsCategory::ELEMENTS);
gArgs.AddArg("-con_signed_blocks", "Signed blockchain. Uses input of `-signblockscript` to define what signatures are necessary to solve it.", false, OptionsCategory::CHAINPARAMS);
@ -61,9 +62,11 @@ std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const std::string& chain
return MakeUnique<CBaseChainParams>("testnet3", 18332, 8332);
else if (chain == CBaseChainParams::REGTEST)
return MakeUnique<CBaseChainParams>("regtest", 18443, 18332);
else if (chain == CBaseChainParams::LIQUID1)
return MakeUnique<CBaseChainParams>("liquidv1", 7041, 8332);
// ELEMENTS:
return MakeUnique<CBaseChainParams>(chain, 7041, 18332);
return MakeUnique<CBaseChainParams>(chain, 7040, 18332);
}
void SelectBaseParams(const std::string& chain)

View file

@ -20,6 +20,7 @@ public:
static const std::string MAIN;
static const std::string TESTNET;
static const std::string REGTEST;
static const std::string LIQUID1;
const std::string& DataDir() const { return strDataDir; }
int RPCPort() const { return nRPCPort; }

View file

@ -1287,4 +1287,15 @@ static SeedSpec6 pnSeed6_test[] = {
{{0xfd,0x87,0xd8,0x7e,0xeb,0x43,0x47,0xb1,0xe4,0x55,0xd1,0xb0,0x14,0x3f,0xb6,0xdb}, 18333},
{{0xfd,0x87,0xd8,0x7e,0xeb,0x43,0xa0,0x60,0x9e,0x46,0x54,0xdb,0x61,0x3b,0xb2,0x6f}, 18333}
};
static SeedSpec6 pnSeed6_liquidv1[] = {
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x23,0xc4,0x10,0xfe}, 7042},
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x23,0xed,0xb0,0x3f}, 7042},
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x23,0xed,0x51,0x0e}, 7042},
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x23,0xed,0x93,0x15}, 7042},
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x23,0xe3,0x5f,0x6d}, 7042},
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x23,0xe7,0x8d,0xad}, 7042},
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x68,0xc4,0x30,0xb8}, 7042},
{{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x23,0xe7,0x0a,0x93}, 7042}
};
#endif // BITCOIN_CHAINPARAMSSEEDS_H

View file

@ -41,7 +41,7 @@ private:
//! The queue of elements to be processed.
//! As the order of booleans doesn't matter, it is used as a LIFO (stack)
std::vector<T> queue;
std::vector<T*> queue;
//! The number of workers (including the master) that are idle.
int nIdle;
@ -66,7 +66,7 @@ private:
bool Loop(bool fMaster = false)
{
boost::condition_variable& cond = fMaster ? condMaster : condWorker;
std::vector<T> vChecks;
std::vector<T*> vChecks;
vChecks.reserve(nBatchSize);
unsigned int nNow = 0;
bool fOk = true;
@ -105,20 +105,18 @@ private:
// * Try to account for idle jobs which will instantly start helping.
// * Don't do batches smaller than 1 (duh), or larger than nBatchSize.
nNow = std::max(1U, std::min(nBatchSize, (unsigned int)queue.size() / (nTotal + nIdle + 1)));
vChecks.resize(nNow);
for (unsigned int i = 0; i < nNow; i++) {
// We want the lock on the mutex to be as short as possible, so swap jobs from the global
// queue to the local batch vector instead of copying.
vChecks[i].swap(queue.back());
queue.pop_back();
}
vChecks.clear();
vChecks.insert(vChecks.end(), queue.end() - nNow, queue.end());
queue.resize(queue.size() - nNow);
// Check whether we need to do work at all
fOk = fAllOk;
}
// execute work
for (T& check : vChecks)
for (T* check : vChecks) {
if (fOk)
fOk = check();
fOk = (*check)();
delete check;
}
vChecks.clear();
} while (true);
}
@ -143,13 +141,10 @@ public:
}
//! Add a batch of checks to the queue
void Add(std::vector<T>& vChecks)
void Add(const std::vector<T*> vChecks)
{
boost::unique_lock<boost::mutex> lock(mutex);
for (T& check : vChecks) {
queue.push_back(T());
check.swap(queue.back());
}
queue.insert(queue.end(), vChecks.begin(), vChecks.end());
nTodo += vChecks.size();
if (vChecks.size() == 1)
condWorker.notify_one();
@ -159,6 +154,7 @@ public:
~CCheckQueue()
{
assert(queue.empty());
}
};
@ -171,14 +167,14 @@ template <typename T>
class CCheckQueueControl
{
private:
CCheckQueue<T> * const pqueue;
CCheckQueue<T>* const pqueue;
bool fDone;
public:
CCheckQueueControl() = delete;
CCheckQueueControl(const CCheckQueueControl&) = delete;
CCheckQueueControl& operator=(const CCheckQueueControl&) = delete;
explicit CCheckQueueControl(CCheckQueue<T> * const pqueueIn) : pqueue(pqueueIn), fDone(false)
explicit CCheckQueueControl(CCheckQueue<T>* const pqueueIn) : pqueue(pqueueIn), fDone(false)
{
// passed queue is supposed to be unused, or nullptr
if (pqueue != nullptr) {
@ -195,7 +191,7 @@ public:
return fRet;
}
void Add(std::vector<T>& vChecks)
void Add(std::vector<T*> vChecks)
{
if (pqueue != nullptr)
pqueue->Add(vChecks);

View file

@ -124,7 +124,16 @@ bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
return true;
}
static const Coin coinEmpty;
// ELEMENTS:
// Because g_con_elementsmode is only set after the moment coinEmpty is initialized,
// we have to force set it to an empty coin without the default asset commitment.
Coin generateEmptyCoin() {
Coin coin;
coin.out.nValue.vchCommitment.clear();
coin.out.nAsset.vchCommitment.clear();
return coin;
}
static const Coin coinEmpty = generateEmptyCoin();
const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
CCoinsMap::const_iterator it = FetchCoin(outpoint);
@ -306,7 +315,8 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
CAmount nResult = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++)
nResult += AccessCoin(tx.vin[i].prevout).out.nValue;
// ELEMENTS: this method is for tests only, just naively add amounts
nResult += AccessCoin(tx.vin[i].prevout).out.nValue.GetAmount();
return nResult;
}

View file

@ -95,14 +95,42 @@ public:
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
if (!ser_action.ForRead()) {
uint64_t nVal = CompressAmount(txout.nValue);
READWRITE(VARINT(nVal));
if (g_con_elementsmode) {
if (!ser_action.ForRead()) {
if (txout.nValue.IsExplicit()) {
uint8_t b = 0;
READWRITE(b);
uint64_t nVal = CompressAmount(txout.nValue.GetAmount());
READWRITE(VARINT(nVal));
} else {
uint8_t b = 1;
READWRITE(b);
READWRITE(txout.nValue);
}
} else {
uint8_t type = 0;
READWRITE(type);
if (type == 0) {
uint64_t nVal = 0;
READWRITE(VARINT(nVal));
txout.nValue = DecompressAmount(nVal);
} else {
READWRITE(txout.nValue);
}
}
READWRITE(txout.nAsset);
} else {
uint64_t nVal = 0;
READWRITE(VARINT(nVal));
txout.nValue = DecompressAmount(nVal);
if (!ser_action.ForRead()) {
assert(txout.nValue.IsExplicit());
uint64_t nVal = CompressAmount(txout.nValue.GetAmount());
READWRITE(VARINT(nVal));
} else {
uint64_t nVal = 0;
READWRITE(VARINT(nVal));
txout.nValue = DecompressAmount(nVal);
}
}
CScriptCompressor cscript(REF(txout.scriptPubKey));
READWRITE(cscript);
}

View file

@ -0,0 +1,425 @@
#include <confidential_validation.h>
#include <issuance.h>
#include <pegins.h>
#include <script/sigcache.h>
#include <blind.h>
namespace {
static secp256k1_context *secp256k1_ctx_verify_amounts;
class CSecp256k1Init {
public:
CSecp256k1Init() {
assert(secp256k1_ctx_verify_amounts == NULL);
secp256k1_ctx_verify_amounts = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
assert(secp256k1_ctx_verify_amounts != NULL);
}
~CSecp256k1Init() {
assert(secp256k1_ctx_verify_amounts != NULL);
secp256k1_context_destroy(secp256k1_ctx_verify_amounts);
secp256k1_ctx_verify_amounts = NULL;
}
};
static CSecp256k1Init instance_of_csecp256k1;
}
bool HasValidFee(const CTransaction& tx) {
CAmountMap totalFee;
for (unsigned int i = 0; i < tx.vout.size(); i++) {
CAmount fee = 0;
if (tx.vout[i].IsFee()) {
fee = tx.vout[i].nValue.GetAmount();
if (fee == 0 || !MoneyRange(fee))
return false;
totalFee[tx.vout[i].nAsset.GetAsset()] += fee;
}
}
return MoneyRange(totalFee);
}
CAmountMap GetFeeMap(const CTransaction& tx) {
CAmountMap fee;
for (const CTxOut& txout : tx.vout) {
if (txout.IsFee()) {
fee[txout.nAsset.GetAsset()] += txout.nValue.GetAmount();
}
}
return fee;
}
bool CRangeCheck::operator()() {
if (val->IsExplicit()) {
return true;
}
if (!CachingRangeProofChecker(store).VerifyRangeProof(rangeproof, val->vchCommitment, assetCommitment, scriptPubKey, secp256k1_ctx_verify_amounts)) {
error = SCRIPT_ERR_RANGEPROOF;
return false;
}
return true;
};
bool CBalanceCheck::operator()() {
if (!secp256k1_pedersen_verify_tally(secp256k1_ctx_verify_amounts, vpCommitsIn.data(), vpCommitsIn.size(), vpCommitsOut.data(), vpCommitsOut.size())) {
error = SCRIPT_ERR_PEDERSEN_TALLY;
return false;
}
return true;
}
bool CSurjectionCheck::operator()() {
return CachingSurjectionProofChecker(store).VerifySurjectionProof(proof, vTags, gen, secp256k1_ctx_verify_amounts, wtxid);
}
// Destroys the check in the case of no queue, or passes its ownership to the queue.
ScriptError QueueCheck(std::vector<CCheck*>* queue, CCheck* check) {
if (queue != NULL) {
queue->push_back(check);
return SCRIPT_ERR_OK;
}
bool success = (*check)();
ScriptError err = check->GetScriptError();
delete check;
return success ? SCRIPT_ERR_OK : err;
}
// Helper function for VerifyAmount(), not exported
static bool VerifyIssuanceAmount(secp256k1_pedersen_commitment& value_commit, secp256k1_generator& asset_gen,
const CAsset& asset, const CConfidentialValue& value, const std::vector<unsigned char>& rangeproof,
std::vector<CCheck*>* checks, const bool store_result)
{
// This is used to add in the explicit values
unsigned char explicit_blinds[32];
memset(explicit_blinds, 0, sizeof(explicit_blinds));
int ret;
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &asset_gen, asset.begin());
assert(ret == 1);
// Build value commitment
if (value.IsExplicit()) {
if (!MoneyRange(value.GetAmount()) || value.GetAmount() == 0) {
return false;
}
if (!rangeproof.empty()) {
return false;
}
ret = secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &value_commit, explicit_blinds, value.GetAmount(), &asset_gen);
// The explicit_blinds are all 0, and the amount is not 0. So secp256k1_pedersen_commit does not fail.
assert(ret == 1);
} else if (value.IsCommitment()) {
// Verify range proof
std::vector<unsigned char> vchAssetCommitment(CConfidentialAsset::nExplicitSize);
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, vchAssetCommitment.data(), &asset_gen);
if (QueueCheck(checks, new CRangeCheck(&value, rangeproof, vchAssetCommitment, CScript(), store_result)) != SCRIPT_ERR_OK) {
return false;
}
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &value_commit, value.vchCommitment.data()) != 1) {
return false;
}
} else {
return false;
}
return true;
}
bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, std::vector<CCheck*>* checks, const bool store_result) {
assert(!tx.IsCoinBase());
assert(inputs.size() == tx.vin.size());
std::vector<secp256k1_pedersen_commitment> vData;
std::vector<secp256k1_pedersen_commitment *> vpCommitsIn, vpCommitsOut;
vData.reserve((tx.vin.size() + tx.vout.size() + GetNumIssuances(tx)));
secp256k1_pedersen_commitment *p = vData.data();
secp256k1_pedersen_commitment commit;
secp256k1_generator gen;
// This is used to add in the explicit values
unsigned char explicit_blinds[32] = {0};
int ret;
uint256 wtxid(tx.GetWitnessHash());
// This list is used to verify surjection proofs.
// Proofs must be constructed with the list being in
// order of input and non-null issuance pseudo-inputs, with
// input first, asset issuance second, reissuance token third.
std::vector<secp256k1_generator> target_generators;
target_generators.reserve(tx.vin.size() + GetNumIssuances(tx));
// Tally up value commitments, check balance
for (size_t i = 0; i < tx.vin.size(); ++i) {
const CConfidentialValue& val = inputs[i].nValue;
const CConfidentialAsset& asset = inputs[i].nAsset;
if (val.IsNull() || asset.IsNull())
return false;
if (asset.IsExplicit()) {
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
assert(ret != 0);
}
else if (asset.IsCommitment()) {
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
return false;
}
else {
return false;
}
target_generators.push_back(gen);
if (val.IsExplicit()) {
if (!MoneyRange(val.GetAmount()))
return false;
// Fails if val.GetAmount() == 0
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explicit_blinds, val.GetAmount(), &gen) != 1)
return false;
} else if (val.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &val.vchCommitment[0]) != 1)
return false;
} else {
return false;
}
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
// Each transaction input may have up to two "pseudo-inputs" to add to the LHS
// for (re)issuance and may require up to two rangeproof checks:
// blinded value of the new assets being made
// blinded value of the issuance tokens being made (only for initial issuance)
const CAssetIssuance& issuance = tx.vin[i].assetIssuance;
// No issuances to process, continue to next input
if (issuance.IsNull()) {
continue;
}
CAsset assetID;
CAsset assetTokenID;
// First construct the assets of the issuances and reissuance token
// These are calculated differently depending on if initial issuance or followup
// New issuance, compute the asset ids
if (issuance.assetBlindingNonce.IsNull()) {
uint256 entropy;
GenerateAssetEntropy(entropy, tx.vin[i].prevout, issuance.assetEntropy);
CalculateAsset(assetID, entropy);
// Null nAmount is considered explicit 0, so just check for commitment
CalculateReissuanceToken(assetTokenID, entropy, issuance.nAmount.IsCommitment());
} else {
// Re-issuance
// hashAssetIdentifier doubles as the entropy on reissuance
CalculateAsset(assetID, issuance.assetEntropy);
CalculateReissuanceToken(assetTokenID, issuance.assetEntropy, issuance.nAmount.IsCommitment());
// Must check that prevout is the blinded issuance token
// prevout's asset tag = assetTokenID + assetBlindingNonce
if (secp256k1_generator_generate_blinded(secp256k1_ctx_verify_amounts, &gen, assetTokenID.begin(), issuance.assetBlindingNonce.begin()) != 1) {
return false;
}
// Serialize the generator for direct comparison
unsigned char derived_generator[33];
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, derived_generator, &gen);
// Belt-and-suspenders: Check that asset commitment from issuance input is correct size
if (asset.vchCommitment.size() != sizeof(derived_generator)) {
return false;
}
// We have already checked the outputs' generator commitment for general validity, so directly compare serialized bytes
if (memcmp(asset.vchCommitment.data(), derived_generator, sizeof(derived_generator))) {
return false;
}
}
// Process issuance of asset
if (!issuance.nAmount.IsValid()) {
return false;
}
if (!issuance.nAmount.IsNull()) {
// Note: This check disallows issuances in transactions with *no* witness data.
// This can be relaxed in a future update as a HF by passing in an empty rangeproof
// to `VerifyIssuanceAmount` instead.
if (i >= tx.witness.vtxinwit.size()) {
return false;
}
if (!VerifyIssuanceAmount(commit, gen, assetID, issuance.nAmount, tx.witness.vtxinwit[i].vchIssuanceAmountRangeproof, checks, store_result)) {
return false;
}
target_generators.push_back(gen);
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
}
// Process issuance of reissuance tokens
if (!issuance.nInflationKeys.IsValid()) {
return false;
}
if (!issuance.nInflationKeys.IsNull()) {
// Only initial issuance can have reissuance tokens
if (!issuance.assetBlindingNonce.IsNull()) {
return false;
}
// Note: This check disallows issuances in transactions with *no* witness data.
// This can be relaxed in a future update as a HF by passing in an empty rangeproof
// to `VerifyIssuanceAmount` instead.
if (i >= tx.witness.vtxinwit.size()) {
return false;
}
if (!VerifyIssuanceAmount(commit, gen, assetTokenID, issuance.nInflationKeys, tx.witness.vtxinwit[i].vchInflationKeysRangeproof, checks, store_result)) {
return false;
}
target_generators.push_back(gen);
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
}
}
for (size_t i = 0; i < tx.vout.size(); ++i)
{
const CConfidentialValue& val = tx.vout[i].nValue;
const CConfidentialAsset& asset = tx.vout[i].nAsset;
if (!asset.IsValid())
return false;
if (!val.IsValid())
return false;
if (!tx.vout[i].nNonce.IsValid())
return false;
if (asset.IsExplicit()) {
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
assert(ret != 0);
}
else if (asset.IsCommitment()) {
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
return false;
}
else {
return false;
}
if (val.IsExplicit()) {
if (!MoneyRange(val.GetAmount()))
return false;
if (val.GetAmount() == 0) {
if (tx.vout[i].scriptPubKey.IsUnspendable()) {
continue;
} else {
// No spendable 0-value outputs
// Reason: A spendable output of 0 reissuance tokens would allow reissuance without reissuance tokens.
return false;
}
}
ret = secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explicit_blinds, val.GetAmount(), &gen);
// The explicit_blinds are all 0, and the amount is not 0. So secp256k1_pedersen_commit does not fail.
assert(ret == 1);
}
else if (val.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &val.vchCommitment[0]) != 1)
return false;
} else {
return false;
}
vData.push_back(commit);
vpCommitsOut.push_back(p);
p++;
}
// Check balance
if (QueueCheck(checks, new CBalanceCheck(vData, vpCommitsIn, vpCommitsOut)) != SCRIPT_ERR_OK) {
return false;
}
// Range proofs
for (size_t i = 0; i < tx.vout.size(); i++) {
const CConfidentialValue& val = tx.vout[i].nValue;
const CConfidentialAsset& asset = tx.vout[i].nAsset;
std::vector<unsigned char> vchAssetCommitment = asset.vchCommitment;
const CTxOutWitness* ptxoutwit = tx.witness.vtxoutwit.size() <= i? NULL: &tx.witness.vtxoutwit[i];
if (val.IsExplicit())
{
if (ptxoutwit && !ptxoutwit->vchRangeproof.empty())
return false;
continue;
}
if (asset.IsExplicit()) {
int ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
assert(ret != 0);
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &vchAssetCommitment[0], &gen);
}
if (!ptxoutwit) {
return false;
}
if (QueueCheck(checks, new CRangeCheck(&val, ptxoutwit->vchRangeproof, vchAssetCommitment, tx.vout[i].scriptPubKey, store_result)) != SCRIPT_ERR_OK) {
return false;
}
}
// Surjection proofs
for (size_t i = 0; i < tx.vout.size(); i++)
{
const CConfidentialAsset& asset = tx.vout[i].nAsset;
const CTxOutWitness* ptxoutwit = tx.witness.vtxoutwit.size() <= i? NULL: &tx.witness.vtxoutwit[i];
// No need for surjection proof
if (asset.IsExplicit()) {
if (ptxoutwit && !ptxoutwit->vchSurjectionproof.empty()) {
return false;
}
continue;
}
if (!ptxoutwit)
return false;
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
return false;
secp256k1_surjectionproof proof;
if (secp256k1_surjectionproof_parse(secp256k1_ctx_verify_amounts, &proof, &ptxoutwit->vchSurjectionproof[0], ptxoutwit->vchSurjectionproof.size()) != 1)
return false;
if (QueueCheck(checks, new CSurjectionCheck(proof, target_generators, gen, wtxid, store_result)) != SCRIPT_ERR_OK) {
return false;
}
}
return true;
}
bool VerifyCoinbaseAmount(const CTransaction& tx, const CAmountMap& mapFees) {
assert(tx.IsCoinBase());
CAmountMap remaining = mapFees;
for (unsigned int i = 0; i < tx.vout.size(); i++) {
const CTxOut& out = tx.vout[i];
if (!out.nValue.IsExplicit() || !out.nAsset.IsExplicit()) {
return false;
}
if (!MoneyRange(out.nValue.GetAmount())) {
return false;
}
if (g_con_elementsmode &&
out.nValue.GetAmount() == 0 && !out.scriptPubKey.IsUnspendable()) {
return false;
}
remaining[out.nAsset.GetAsset()] -= out.nValue.GetAmount();
}
return MoneyRange(remaining);
}

View file

@ -0,0 +1,97 @@
#ifndef BITCOIN_CONFIDENTIAL_VALIDATION_H
#define BITCOIN_CONFIDENTIAL_VALIDATION_H
#include <amount.h>
#include <asset.h>
#include <coins.h>
#include <primitives/confidential.h>
#include <primitives/transaction.h>
#include <script/script_error.h>
#include <secp256k1.h>
#include <secp256k1_rangeproof.h>
#include <secp256k1_surjectionproof.h>
#include <uint256.h>
// Check if explicit TX fees overflow or are negative
bool HasValidFee(const CTransaction& tx);
// Compute the fee from the explicit fee outputs. Must call HasValidFee first
CAmountMap GetFeeMap(const CTransaction& tx);
/**
* ELEMENTS:
* Closure representing one verification, either script or range checks.
* Note that this stores references to the spending transaction
*/
class CCheck
{
protected:
ScriptError error;
public:
CCheck() : error(SCRIPT_ERR_UNKNOWN_ERROR) {}
virtual ~CCheck() {}
virtual bool operator()() = 0;
ScriptError GetScriptError() const { return error; }
};
/** Closure representing one output range check. */
class CRangeCheck : public CCheck
{
private:
const CConfidentialValue* val;
const std::vector<unsigned char>& rangeproof;
// *Must* be a commitment, not an explicit value
const std::vector<unsigned char> assetCommitment;
const CScript scriptPubKey;
const bool store;
public:
CRangeCheck(const CConfidentialValue* val_, const std::vector<unsigned char>& rangeproof_, const std::vector<unsigned char>& assetCommitment_, const CScript& scriptPubKey_, const bool storeIn) : val(val_), rangeproof(rangeproof_), assetCommitment(assetCommitment_), scriptPubKey(scriptPubKey_), store(storeIn) {}
bool operator()();
};
/** Closure representing a transaction amount balance check. */
class CBalanceCheck : public CCheck
{
private:
std::vector<secp256k1_pedersen_commitment> vData;
std::vector<secp256k1_pedersen_commitment *> vpCommitsIn, vpCommitsOut;
public:
CBalanceCheck(std::vector<secp256k1_pedersen_commitment>& vData_, std::vector<secp256k1_pedersen_commitment*>& vpCommitsIn_, std::vector<secp256k1_pedersen_commitment*>& vpCommitsOut_) {
vData.swap(vData_);
vpCommitsIn.swap(vpCommitsIn_);
vpCommitsOut.swap(vpCommitsOut_);
}
bool operator()();
};
class CSurjectionCheck : public CCheck
{
private:
secp256k1_surjectionproof proof;
std::vector<secp256k1_generator> vTags;
secp256k1_generator gen;
uint256 wtxid;
const bool store;
public:
CSurjectionCheck(secp256k1_surjectionproof& proof_in, std::vector<secp256k1_generator>& tags_in, secp256k1_generator& gen_in, uint256& wtxid_in, const bool store_in) : proof(proof_in), vTags(tags_in), gen(gen_in), wtxid(wtxid_in), store(store_in) {}
bool operator()();
};
ScriptError QueueCheck(std::vector<CCheck*>* queue, CCheck* check);
bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, std::vector<CCheck*>* pvChecks, const bool cacheStore);
bool VerifyCoinbaseAmount(const CTransaction& tx, const CAmountMap& mapFees);
#endif // BITCOIN_CONFIDENTIAL_VALIDATION_H

View file

@ -77,13 +77,17 @@ uint256 BlockWitnessMerkleRoot(const CBlock& block, bool* mutated)
{
std::vector<uint256> leaves;
leaves.resize(block.vtx.size());
leaves[0].SetNull(); // The witness hash of the coinbase is 0.
for (size_t s = 1; s < block.vtx.size(); s++) {
if (g_con_elementswitness) {
if (g_con_elementsmode) {
// Coinbase witness hash for inputs is just CTxInWitness().GetHash()
for (size_t s = 0; s < block.vtx.size(); s++) {
leaves[s] = block.vtx[s]->GetWitnessOnlyHash();
} else {
}
return ComputeFastMerkleRoot(std::move(leaves));
} else {
leaves[0].SetNull(); // The witness hash of the coinbase is 0.
for (size_t s = 1; s < block.vtx.size(); s++) {
leaves[s] = block.vtx[s]->GetWitnessHash();
}
return ComputeMerkleRoot(std::move(leaves), mutated);
}
return ComputeMerkleRoot(std::move(leaves), mutated);
}

View file

@ -6,6 +6,7 @@
#ifndef BITCOIN_CONSENSUS_PARAMS_H
#define BITCOIN_CONSENSUS_PARAMS_H
#include <asset.h>
#include <uint256.h>
#include <limits>
#include <map>
@ -83,6 +84,7 @@ struct Params {
// ELEMENTS CHAIN PARAMS
CScript mandatory_coinbase_destination;
CAmount genesis_subsidy;
CAsset subsidy_asset;
bool connect_genesis_outputs;
bool has_parent_chain;
uint256 parentChainPowLimit;
@ -90,6 +92,8 @@ struct Params {
CScript parent_chain_signblockscript;
bool ParentChainHasPow() const { return parent_chain_signblockscript == CScript();}
CScript fedpegScript;
CAsset pegged_asset;
CAsset parent_pegged_asset;
// g_con_blockheightinheader global hack instead of proper arg due to circular dep
std::string genesis_style;
CScript signblockscript;

View file

@ -190,15 +190,19 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize");
// Check for negative or overflow output values
CAmount nValueOut = 0;
CAmount nValueOutExplicit = 0;
for (const auto& txout : tx.vout)
{
if (txout.nValue < 0)
if (!txout.nValue.IsValid())
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-amount-invalid");
if (!txout.nValue.IsExplicit())
continue;
if (txout.nValue.GetAmount() < 0)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative");
if (txout.nValue > MAX_MONEY)
if (txout.nValue.GetAmount() > MAX_MONEY)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge");
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
nValueOutExplicit += txout.nValue.GetAmount();
if (!MoneyRange(nValueOutExplicit))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
}
@ -216,6 +220,12 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
{
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.DoS(100, false, REJECT_INVALID, "bad-cb-length");
for (unsigned int i = 0; i < tx.vout.size(); i++) {
if (tx.vout[i].IsFee()) {
return state.DoS(100, false, REJECT_INVALID, "bad-cb-fee");
}
}
}
else
{
@ -228,7 +238,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
}
namespace Consensus {
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent)
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent, std::vector<CCheck*> *pvChecks, const bool cacheStore, bool fScriptChecks)
{
// are the actual inputs available?
if (!inputs.HaveInputs(tx)) {
@ -236,6 +246,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
strprintf("%s: inputs missing/spent", __func__));
}
std::vector<CTxOut> spent_inputs;
CAmount nValueIn = 0;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
const COutPoint &prevout = tx.vin[i].prevout;
@ -243,7 +254,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
// Check existence and validity of pegin witness
std::string err;
if (tx.witness.vtxinwit.size() <= i || !IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, prevout, err, true)) {
return state.DoS(0, false, REJECT_PEGIN, "bad-pegin-witness");
return state.DoS(0, false, REJECT_PEGIN, "bad-pegin-witness", false, err);
}
std::pair<uint256, COutPoint> pegin = std::make_pair(uint256(tx.witness.vtxinwit[i].m_pegin_witness.stack[2]), prevout);
if (inputs.IsPeginSpent(pegin)) {
@ -256,11 +267,12 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
setPeginsSpent.insert(pegin);
// Tally the input amount.
const CTxOut out = GetPeginOutputFromWitness(tx.witness.vtxinwit[i].m_pegin_witness);
if (!MoneyRange(out.nValue)) {
spent_inputs.push_back(GetPeginOutputFromWitness(tx.witness.vtxinwit[i].m_pegin_witness));
const CTxOut& out = spent_inputs.back();
nValueIn += out.nValue.GetAmount(); // Non-explicit already filtered by IsValidPeginWitness
if (!MoneyRange(out.nValue.GetAmount())) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-pegin-inputvalue-outofrange");
}
nValueIn += out.nValue;
} else {
const Coin& coin = inputs.AccessCoin(prevout);
assert(!coin.IsSpent());
@ -271,29 +283,40 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
}
// Check for negative or overflow input values
nValueIn += coin.out.nValue;
if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
spent_inputs.push_back(coin.out);
if (coin.out.nValue.IsExplicit()) {
nValueIn += coin.out.nValue.GetAmount();
}
}
}
//TODO(rebase) you need to replace these two blocks with the `VerifyAmounts` and `HasValidFee` methods
const CAmount value_out = tx.GetValueOut();
if (nValueIn < value_out) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out)));
if (g_con_elementsmode) {
// Tally transaction fees
if (!HasValidFee(tx)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
}
// Verify that amounts add up.
if (fScriptChecks && !VerifyAmounts(spent_inputs, tx, pvChecks, cacheStore)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-ne-out", false, "value in != value out");
}
fee_map += GetFeeMap(tx);
} else {
const CAmount value_out = tx.GetValueOutMap()[CAsset()];
if (nValueIn < value_out) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out)));
}
// Tally transaction fees
const CAmount txfee_aux = nValueIn - value_out;
if (!MoneyRange(txfee_aux)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
}
fee_map[CAsset()] += txfee_aux;
}
// Tally transaction fees
const CAmount txfee_aux = nValueIn - value_out;
if (!MoneyRange(txfee_aux)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
}
txfee = txfee_aux;
return true;
}
}// namespace Consensus

View file

@ -7,6 +7,7 @@
#include <amount.h>
#include <confidential_validation.h>
#include <set>
#include <stdint.h>
#include <vector>
@ -27,10 +28,10 @@ namespace Consensus {
/**
* Check whether all inputs of this transaction are valid (no double spends and amounts)
* This does not modify the UTXO set. This does not check scripts and sigs.
* @param[out] txfee Set to the transaction fee if successful.
* @param[out] fee_map Set to the transaction fee if successful.
* Preconditions: tx.IsCoinBase() is false.
*/
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent);
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent, std::vector<CCheck*> *pvChecks, const bool cacheStore, bool fScriptChecks);
} // namespace Consensus
/** Auxiliary functions for transaction validation (ideally should not be exposed) */

View file

@ -108,11 +108,14 @@ static inline int64_t GetTransactionInputWeight(const CTransaction& tx, const si
{
// scriptWitness size is added here because witnesses and txins are split up in segwit serialization.
assert(tx.witness.vtxinwit.size() > nIn);
//TODO(rebase) only count CA/CT witnesses when g_con_elementswitness is true
// ELEMENTS: This is only used for change size calculation in wallet, assert if
// anything is unexpected for this call e.g. issuances, rangeproofs
assert(tx.witness.vtxinwit[nIn].vchIssuanceAmountRangeproof.empty());
assert(tx.witness.vtxinwit[nIn].vchInflationKeysRangeproof.empty());
return ::GetSerializeSize(tx.vin[nIn], PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1)
+ ::GetSerializeSize(tx.vin[nIn], PROTOCOL_VERSION)
+ ::GetSerializeSize(tx.witness.vtxinwit[nIn].scriptWitness.stack, PROTOCOL_VERSION)
+ ::GetSerializeSize(tx.witness.vtxinwit[nIn].m_pegin_witness.stack, PROTOCOL_VERSION);
+ ::GetSerializeSize(tx.witness.vtxinwit[nIn].scriptWitness.stack, PROTOCOL_VERSION);
}
#endif // BITCOIN_CONSENSUS_VALIDATION_H

View file

@ -6,6 +6,7 @@
#define BITCOIN_CORE_IO_H
#include <amount.h>
#include <asset.h>
#include <string>
#include <vector>

View file

@ -6,6 +6,7 @@
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <issuance.h>
#include <key_io.h>
#include <script/script.h>
#include <script/standard.h>
@ -16,6 +17,32 @@
#include <utilmoneystr.h>
#include <utilstrencodings.h>
#include <secp256k1_rangeproof.h>
static secp256k1_context* secp256k1_blind_context = NULL;
class RPCRawTransaction_ECC_Init {
public:
RPCRawTransaction_ECC_Init() {
assert(secp256k1_blind_context == NULL);
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
assert(ctx != NULL);
secp256k1_blind_context = ctx;
}
~RPCRawTransaction_ECC_Init() {
secp256k1_context *ctx = secp256k1_blind_context;
secp256k1_blind_context = NULL;
if (ctx) {
secp256k1_context_destroy(ctx);
}
}
};
static RPCRawTransaction_ECC_Init ecc_init_on_load;
UniValue ValueFromAmount(const CAmount& amount)
{
bool sign = amount < 0;
@ -200,7 +227,7 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
{
entry.pushKV("txid", tx.GetHash().GetHex());
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
if (g_con_elementswitness) {
if (g_con_elementsmode) {
entry.pushKV("wtxid", tx.GetWitnessHash().GetHex());
entry.pushKV("withash", tx.GetWitnessOnlyHash().GetHex());
}
@ -223,29 +250,65 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
o.pushKV("asm", ScriptToAsmStr(txin.scriptSig, true));
o.pushKV("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()));
in.pushKV("scriptSig", o);
if (tx.witness.vtxinwit.size() > i) {
const CScriptWitness &scriptWitness = tx.witness.vtxinwit[i].scriptWitness;
if (!scriptWitness.IsNull()) {
UniValue txinwitness(UniValue::VARR);
for (const auto &item : scriptWitness.stack) {
txinwitness.push_back(HexStr(item.begin(), item.end()));
}
in.pushKV("txinwitness", txinwitness);
}
}
// ELEMENTS:
in.pushKV("is_pegin", txin.m_is_pegin);
if (tx.witness.vtxinwit.size() > i && !tx.witness.vtxinwit[i].m_pegin_witness.IsNull()) {
UniValue pegin_witness(UniValue::VARR);
for (const auto& item : tx.witness.vtxinwit[i].m_pegin_witness.stack) {
pegin_witness.push_back(HexStr(item.begin(), item.end()));
}
in.pushKV("pegin_witness", pegin_witness);
}
}
in.pushKV("sequence", (int64_t)txin.nSequence);
if (tx.witness.vtxinwit.size() > i) {
const CScriptWitness &scriptWitness = tx.witness.vtxinwit[i].scriptWitness;
if (!scriptWitness.IsNull()) {
UniValue txinwitness(UniValue::VARR);
for (const auto &item : scriptWitness.stack) {
txinwitness.push_back(HexStr(item.begin(), item.end()));
}
in.pushKV("txinwitness", txinwitness);
}
}
// ELEMENTS:
if (tx.witness.vtxinwit.size() > i && !tx.witness.vtxinwit[i].m_pegin_witness.IsNull()) {
UniValue pegin_witness(UniValue::VARR);
for (const auto& item : tx.witness.vtxinwit[i].m_pegin_witness.stack) {
pegin_witness.push_back(HexStr(item.begin(), item.end()));
}
in.pushKV("pegin_witness", pegin_witness);
}
const CAssetIssuance& issuance = txin.assetIssuance;
if (!issuance.IsNull()) {
UniValue issue(UniValue::VOBJ);
issue.pushKV("assetBlindingNonce", issuance.assetBlindingNonce.GetHex());
CAsset asset;
CAsset token;
uint256 entropy;
if (issuance.assetBlindingNonce.IsNull()) {
GenerateAssetEntropy(entropy, txin.prevout, issuance.assetEntropy);
issue.pushKV("assetEntropy", entropy.GetHex());
CalculateAsset(asset, entropy);
CalculateReissuanceToken(token, entropy, issuance.nAmount.IsCommitment());
issue.pushKV("isreissuance", false);
issue.pushKV("token", token.GetHex());
}
else {
issue.pushKV("assetEntropy", issuance.assetEntropy.GetHex());
issue.pushKV("isreissuance", true);
CalculateAsset(asset, issuance.assetEntropy);
}
issue.pushKV("asset", asset.GetHex());
if (issuance.nAmount.IsExplicit()) {
issue.pushKV("assetamount", ValueFromAmount(issuance.nAmount.GetAmount()));
} else if (issuance.nAmount.IsCommitment()) {
issue.pushKV("assetamountcommitment", HexStr(issuance.nAmount.vchCommitment));
}
if (issuance.nInflationKeys.IsExplicit()) {
issue.pushKV("tokenamount", ValueFromAmount(issuance.nInflationKeys.GetAmount()));
} else if (issuance.nInflationKeys.IsCommitment()) {
issue.pushKV("tokenamountcommitment", HexStr(issuance.nInflationKeys.vchCommitment));
}
in.pushKV("issuance", issue);
}
// END ELEMENTS
vin.push_back(in);
}
entry.pushKV("vin", vin);
@ -256,7 +319,37 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry,
UniValue out(UniValue::VOBJ);
out.pushKV("value", ValueFromAmount(txout.nValue));
if (txout.nValue.IsExplicit()) {
out.pushKV("value", ValueFromAmount(txout.nValue.GetAmount()));
} else {
int exp;
int mantissa;
uint64_t minv;
uint64_t maxv;
const CTxOutWitness* ptxoutwit = tx.witness.vtxoutwit.size() <= i? NULL: &tx.witness.vtxoutwit[i];
if (ptxoutwit && secp256k1_rangeproof_info(secp256k1_blind_context, &exp, &mantissa, &minv, &maxv, &ptxoutwit->vchRangeproof[0], ptxoutwit->vchRangeproof.size())) {
if (exp == -1) {
out.pushKV("value", ValueFromAmount((CAmount)minv));
} else {
out.pushKV("value-minimum", ValueFromAmount((CAmount)minv));
out.pushKV("value-maximum", ValueFromAmount((CAmount)maxv));
}
out.pushKV("ct-exponent", exp);
out.pushKV("ct-bits", mantissa);
}
out.pushKV("valuecommitment", txout.nValue.GetHex());
}
if (g_con_elementsmode) {
if (txout.nAsset.IsExplicit()) {
out.pushKV("asset", txout.nAsset.GetAsset().GetHex());
} else {
out.pushKV("assetcommitment", txout.nAsset.GetHex());
}
out.pushKV("commitmentnonce", txout.nNonce.GetHex());
CPubKey pubkey(txout.nNonce.vchCommitment);
out.pushKV("commitmentnonce_fully_valid", pubkey.IsFullyValid());
}
out.pushKV("n", (int64_t)i);
UniValue o(UniValue::VOBJ);

View file

@ -64,6 +64,8 @@
#include <openssl/crypto.h>
#include <primitives/pak.h> // CPAKList
#include <assetsdir.h> // InitGlobalAssetDir
#include <pegins.h>
#if ENABLE_ZMQ
#include <zmq/zmqnotificationinterface.h>
@ -508,6 +510,11 @@ void SetupServerArgs()
gArgs.AddArg("-extpubkeyprefix", strprintf("The 4-byte prefix, in hex, of the chain's base58 extended public key encoding. (default: %s)", HexStr(defaultChainParams->Base58Prefix(CChainParams::EXT_PUBLIC_KEY))), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-extprvkeyprefix", strprintf("The 4-byte prefix, in hex, of the chain's base58 extended private key encoding. (default: %s)", HexStr(defaultChainParams->Base58Prefix(CChainParams::EXT_SECRET_KEY))), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-bech32_hrp", strprintf("The human-readable part of the chain's bech32 encoding. (default: %s)", defaultChainParams->Bech32HRP()), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-blech32_hrp", strprintf("The human-readable part of the chain's blech32 encoding. Used in confidential addresses.(default: %s)", defaultChainParams->Blech32HRP()), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-assetdir", "Entries of pet names of assets, in this format:asset=<hex>:<label>. There can be any number of entries.", false, OptionsCategory::ELEMENTS);
gArgs.AddArg("-defaultpeggedassetname", "Default name of the pegged asset. (default: bitcoin)", false, OptionsCategory::ELEMENTS);
gArgs.AddArg("-blindedaddresses", "Give blind addresses by default via getnewaddress and getrawchangeaddress. (default: -con_elementsmode value)", false, OptionsCategory::ELEMENTS);
gArgs.AddArg("-blindedprefix", "The byte prefix, in decimal, of blinded addresses. (default: 4)", false, OptionsCategory::ELEMENTS);
#if HAVE_DECL_DAEMON
gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS);
@ -534,6 +541,13 @@ void SetupServerArgs()
gArgs.AddArg("-parentpubkeyprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 pubkey address. (default: %d)", 111), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-parentscriptprefix", strprintf("The byte prefix, in decimal, of the parent chain's base58 script address. (default: %d)", 196), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-parent_bech32_hrp", strprintf("The human-readable part of the parent chain's bech32 encoding. (default: %s)", "bc"), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-parent_blech32_hrp", strprintf("The human-readable part of the parent chain's blech32 encoding. (default: %s)", "bc"), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-con_parent_pegged_asset=<hex>", "Asset ID (hex) for pegged asset for when parent chain has CA. (default: 0x00)", false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-feeasset=<hex>", strprintf("Asset ID (hex) for mempool/relay fees (default: %s)", defaultChainParams->GetConsensus().pegged_asset.GetHex()), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-subsidyasset=<hex>", strprintf("Asset ID (hex) for the block subsidy (default: %s)", defaultChainParams->GetConsensus().pegged_asset.GetHex()), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-initialreissuancetokens=<n>", "The amount of reissuance tokens created in the genesis block. (default: 0)", false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-ct_bits", strprintf("The default number of hiding bits in a rangeproof. Will be exceeded to cover amounts exceeding the maximum hiding value. (default: %d)", 36), false, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-ct_exponent", strprintf("The hiding exponent. (default: %s)", 0), false, OptionsCategory::CHAINPARAMS);
// Add the hidden options
gArgs.AddHiddenArgs(hidden_args);
@ -1161,6 +1175,13 @@ bool AppInitParameterInteraction()
}
}
try {
const std::string default_asset_name = gArgs.GetArg("-defaultpeggedassetname", "bitcoin");
InitGlobalAssetDir(gArgs.GetArgs("-assetdir"), default_asset_name);
} catch (const std::exception& e) {
return InitError(strprintf("Error in -assetdir: %s\n", e.what()));
}
return true;
}
@ -1259,6 +1280,9 @@ bool AppInitMain()
InitSignatureCache();
InitScriptExecutionCache();
InitRangeproofCache();
InitSurjectionproofCache();
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);
if (nScriptCheckThreads) {
@ -1282,6 +1306,9 @@ bool AppInitMain()
RegisterZMQRPCCommands(tableRPC);
#endif
// ELEMENTS:
policyAsset = CAsset(uint256S(gArgs.GetArg("-feeasset", chainparams.GetConsensus().pegged_asset.GetHex())));
/* Start the RPC server already. It will be started in "warmup" mode
* and not really process calls already (but it will signify connections
* that the server is there and will be ready later). Warmup mode will
@ -1820,7 +1847,21 @@ bool AppInitMain()
}
}
// ********************************************************* Step 14: finished
// ********************************************************* Step 14: Check fedpeg
// ELEMENTS:
if (chainparams.GetConsensus().has_parent_chain) {
// Will assert if not properly formatted
const CScript& fedpeg_script = chainparams.GetConsensus().fedpegScript;
unsigned int dummy_required;
std::vector<std::vector<unsigned char>> dummy_keys;
if (!MatchLiquidWatchman(fedpeg_script) &&
fedpeg_script != CScript() << OP_TRUE &&
!MatchMultisig(fedpeg_script, dummy_required, dummy_keys)) {
return InitError(_("ERROR: Fedpegscript is not one of the accepted templates: OP_TRUE, CHECKMULTISIG, and Liquidv1"));
}
}
// ********************************************************* Step 15: finished
SetRPCWarmupFinished();

View file

@ -31,7 +31,7 @@ namespace {
class PendingWalletTxImpl : public PendingWalletTx
{
public:
explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet), m_key(&wallet) {}
explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet) { m_keys.reserve(1); m_keys.emplace_back(new CReserveKey(&wallet)); }
const CTransaction& get() override { return *m_tx; }
@ -43,7 +43,7 @@ public:
{
LOCK2(cs_main, m_wallet.cs_wallet);
CValidationState state;
if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_key, g_connman.get(), state)) {
if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_keys, g_connman.get(), state)) {
reject_reason = state.GetRejectReason();
return false;
}
@ -52,7 +52,7 @@ public:
CTransactionRef m_tx;
CWallet& m_wallet;
CReserveKey m_key;
std::vector<std::unique_ptr<CReserveKey>> m_keys;
};
//! Construct wallet tx struct.
@ -224,7 +224,7 @@ public:
{
LOCK2(cs_main, m_wallet.cs_wallet);
auto pending = MakeUnique<PendingWalletTxImpl>(m_wallet);
if (!m_wallet.CreateTransaction(recipients, pending->m_tx, pending->m_key, fee, change_pos,
if (!m_wallet.CreateTransaction(recipients, pending->m_tx, pending->m_keys, fee, change_pos,
fail_reason, coin_control, sign)) {
return {};
}
@ -355,8 +355,8 @@ public:
num_blocks = ::chainActive.Height();
return true;
}
CAmount getBalance() override { return m_wallet.GetBalance(); }
CAmount getAvailableBalance(const CCoinControl& coin_control) override
CAmountMap getBalance() override { return m_wallet.GetBalance(); }
CAmountMap getAvailableBalance(const CCoinControl& coin_control) override
{
return m_wallet.GetAvailableBalance(&coin_control);
}
@ -370,12 +370,12 @@ public:
LOCK2(::cs_main, m_wallet.cs_wallet);
return m_wallet.IsMine(txout);
}
CAmount getDebit(const CTxIn& txin, isminefilter filter) override
CAmountMap getDebit(const CTxIn& txin, isminefilter filter) override
{
LOCK2(::cs_main, m_wallet.cs_wallet);
return m_wallet.GetDebit(txin, filter);
}
CAmount getCredit(const CTxOut& txout, isminefilter filter) override
CAmountMap getCredit(const CTxOut& txout, isminefilter filter) override
{
LOCK2(::cs_main, m_wallet.cs_wallet);
return m_wallet.GetCredit(txout, filter);

View file

@ -6,6 +6,7 @@
#define BITCOIN_INTERFACES_WALLET_H
#include <amount.h> // For CAmount
#include <asset.h> // For CAmountMap
#include <pubkey.h> // For CKeyID and CScriptID (definitions needed in CTxDestination instantiation)
#include <script/ismine.h> // For isminefilter, isminetype
#include <script/standard.h> // For CTxDestination
@ -196,10 +197,10 @@ public:
virtual bool tryGetBalances(WalletBalances& balances, int& num_blocks) = 0;
//! Get balance.
virtual CAmount getBalance() = 0;
virtual CAmountMap getBalance() = 0;
//! Get available balance.
virtual CAmount getAvailableBalance(const CCoinControl& coin_control) = 0;
virtual CAmountMap getAvailableBalance(const CCoinControl& coin_control) = 0;
//! Return whether transaction input belongs to wallet.
virtual isminetype txinIsMine(const CTxIn& txin) = 0;
@ -208,10 +209,10 @@ public:
virtual isminetype txoutIsMine(const CTxOut& txout) = 0;
//! Return debit amount if transaction input belongs to wallet.
virtual CAmount getDebit(const CTxIn& txin, isminefilter filter) = 0;
virtual CAmountMap getDebit(const CTxIn& txin, isminefilter filter) = 0;
//! Return credit amount if transaction input belongs to wallet.
virtual CAmount getCredit(const CTxOut& txout, isminefilter filter) = 0;
virtual CAmountMap getCredit(const CTxOut& txout, isminefilter filter) = 0;
//! Return AvailableCoins + LockedCoins grouped by wallet address.
//! (put change in one group with wallet address)
@ -309,13 +310,13 @@ struct WalletAddress
//! Collection of wallet balances.
struct WalletBalances
{
CAmount balance = 0;
CAmount unconfirmed_balance = 0;
CAmount immature_balance = 0;
CAmountMap balance = CAmountMap();
CAmountMap unconfirmed_balance = CAmountMap();
CAmountMap immature_balance = CAmountMap();
bool have_watch_only = false;
CAmount watch_only_balance = 0;
CAmount unconfirmed_watch_only_balance = 0;
CAmount immature_watch_only_balance = 0;
CAmountMap watch_only_balance = CAmountMap();
CAmountMap unconfirmed_watch_only_balance = CAmountMap();
CAmountMap immature_watch_only_balance = CAmountMap();
bool balanceChanged(const WalletBalances& prev) const
{
@ -334,9 +335,9 @@ struct WalletTx
std::vector<isminetype> txout_is_mine;
std::vector<CTxDestination> txout_address;
std::vector<isminetype> txout_address_is_mine;
CAmount credit;
CAmount debit;
CAmount change;
CAmountMap credit;
CAmountMap debit;
CAmountMap change;
int64_t time;
std::map<std::string, std::string> value_map;
bool is_coinbase;

82
src/issuance.cpp Normal file
View file

@ -0,0 +1,82 @@
#include <issuance.h>
#include <primitives/transaction.h>
#include <amount.h>
void GenerateAssetEntropy(uint256& entropy, const COutPoint& prevout, const uint256& contracthash)
{
// E : entropy
// I : prevout
// C : contract
// E = H( H(I) || H(C) )
std::vector<uint256> leaves;
leaves.reserve(2);
leaves.push_back(SerializeHash(prevout, SER_GETHASH, 0));
leaves.push_back(contracthash);
entropy = ComputeFastMerkleRoot(leaves);
}
void CalculateAsset(CAsset& asset, const uint256& entropy)
{
static const uint256 kZero = uint256S("0x0000000000000000000000000000000000000000000000000000000000000000");
// H_a : asset tag
// E : entropy
// H_a = H( E || 0 )
std::vector<uint256> leaves;
leaves.reserve(2);
leaves.push_back(entropy);
leaves.push_back(kZero);
asset = CAsset(ComputeFastMerkleRoot(leaves));
}
void CalculateReissuanceToken(CAsset& reissuanceToken, const uint256& entropy, bool fConfidential)
{
static const uint256 kOne = uint256S("0x0000000000000000000000000000000000000000000000000000000000000001");
static const uint256 kTwo = uint256S("0x0000000000000000000000000000000000000000000000000000000000000002");
// H_a : asset reissuance tag
// E : entropy
// if not fConfidential:
// H_a = H( E || 1 )
// else
// H_a = H( E || 2 )
std::vector<uint256> leaves;
leaves.reserve(2);
leaves.push_back(entropy);
leaves.push_back(fConfidential ? kTwo : kOne);
reissuanceToken = CAsset(ComputeFastMerkleRoot(leaves));
}
/** Add an issuance transaction to the genesis block. Typically used to pre-issue
* the policyAsset of a blockchain. The genesis block is not actually validated,
* so this transaction simply has to match issuance structure. */
void AppendInitialIssuance(CBlock& genesis_block, const COutPoint& prevout, const uint256& contract, const int64_t asset_outputs, const int64_t asset_values, const int64_t reissuance_outputs, const int64_t reissuance_values, const CScript& issuance_destination) {
uint256 entropy;
GenerateAssetEntropy(entropy, prevout, contract);
CAsset asset;
CalculateAsset(asset, entropy);
// Re-issuance of policyAsset is always unblinded
CAsset reissuance;
CalculateReissuanceToken(reissuance, entropy, false);
// Note: Genesis block isn't actually validated, outputs are entered into utxo db only
CMutableTransaction txNew;
txNew.nVersion = 1;
txNew.vin.resize(1);
txNew.vin[0].prevout = prevout;
txNew.vin[0].assetIssuance.assetEntropy = contract;
txNew.vin[0].assetIssuance.nAmount = CConfidentialValue(asset_values * asset_outputs);
txNew.vin[0].assetIssuance.nInflationKeys = CConfidentialValue(reissuance_values * reissuance_outputs);
for (unsigned int i = 0; i < asset_outputs; i++) {
txNew.vout.push_back(CTxOut(asset, CConfidentialValue(asset_values), issuance_destination));
}
for (unsigned int i = 0; i < reissuance_outputs; i++) {
txNew.vout.push_back(CTxOut(reissuance, CConfidentialValue(reissuance_values), issuance_destination));
}
genesis_block.vtx.push_back(MakeTransactionRef(std::move(txNew)));
genesis_block.hashMerkleRoot = BlockMerkleRoot(genesis_block);
}

48
src/issuance.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef BITCOIN_ISSUANCE_H
#define BITCOIN_ISSUANCE_H
#include <primitives/transaction.h>
#include <amount.h>
#include <hash.h>
#include <consensus/merkle.h>
/**
* Calculate the asset entropy from an COutPoint and a tx-author specified
* Ricardian contract. See Definition 18 of the confidential assets paper.
*
* @param[out] entropy The asset entropy, which is used as input to
* CalculateAsset and CalculateReissuanceToken.
* @param[in] prevout Reference to the UTXO being spent.
* @param[in] contracthash Root hash of the issuer-specified Ricardian
* contract.
*/
void GenerateAssetEntropy(uint256& entropy, const COutPoint& prevout, const uint256& contracthash);
/**
* Derive the asset from the entropy. See Definition 19 of the confidential
* assets paper.
*
* @param[out] asset The nonce used as auxiliary input to the Pedersen
* commitment setup to derive the unblinded asset tag.
* @param[in] entropy The asset entropy returned by GenerateAssetEntropy.
*/
void CalculateAsset(CAsset& asset, const uint256& entropy);
/**
* Derive the asset reissuance token asset from the entropy and reissuance
* parameters (confidential or explicit). See Definition 21 of the confidential
* assets paper.
*
* @param[out] reissuanceToken The nonce used as auxiliary input to the
* Pedersen commitment setup to derive the
* unblinded reissuance asset tag.
* @param[in] entropy The asset entropy returned by GenerateAssetEntropy.
* @param[in] fConfidential Set to true if the initial issuance was blinded,
* false otherwise.
*/
void CalculateReissuanceToken(CAsset& reissuanceToken, const uint256& entropy, bool fConfidential);
void AppendInitialIssuance(CBlock& genesis_block, const COutPoint& prevout, const uint256& contract, const int64_t asset_outputs, const int64_t asset_values, const int64_t reissuance_outputs, const int64_t reissuance_values, const CScript& issuance_destination);
#endif // BITCOIN_ISSUANCE_H

View file

@ -12,6 +12,7 @@
#include <secp256k1.h>
#include <secp256k1_recovery.h>
#include <secp256k1_ecdh.h>
static secp256k1_context* secp256k1_context_sign = nullptr;
@ -202,6 +203,15 @@ bool SigHasLowR(const secp256k1_ecdsa_signature* sig)
return compact_sig[0] < 0x80;
}
uint256 CKey::ECDH(const CPubKey& pubkey) const {
assert(fValid);
uint256 result;
secp256k1_pubkey pkey;
assert(secp256k1_ec_pubkey_parse(secp256k1_context_sign, &pkey, pubkey.begin(), pubkey.size()));
assert(secp256k1_ecdh(secp256k1_context_sign, result.begin(), &pkey, begin(), NULL, NULL));
return result;
}
bool CKey::Sign(const uint256 &hash, std::vector<unsigned char>& vchSig, bool grind, uint32_t test_case) const {
if (!fValid)
return false;

View file

@ -110,6 +110,11 @@ public:
*/
CPubKey GetPubKey() const;
/**
* Compute the ECDH exchange result using this private key and another public key.
*/
uint256 ECDH(const CPubKey& pubkey) const;
/**
* Create a DER-serialized signature.
* The test_case parameter tweaks the deterministic nonce.

View file

@ -6,6 +6,7 @@
#include <base58.h>
#include <bech32.h>
#include <blech32.h>
#include <chainparams.h>
#include <script/script.h>
#include <utilstrencodings.h>
@ -29,16 +30,38 @@ private:
public:
explicit DestinationEncoder(const CChainParams& params, const bool for_parent) : m_params(params), for_parent(for_parent) {}
std::string operator()(const CKeyID& id) const
std::string operator()(const PKHash& id) const
{
if (id.blinding_pubkey.IsFullyValid()) {
assert(!for_parent);
std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::BLINDED_ADDRESS);
// Blinded addresses have the actual address type prefix inside the payload.
std::vector<unsigned char> prefix = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS);
data.insert(data.end(), prefix.begin(), prefix.end());
data.insert(data.end(), id.blinding_pubkey.begin(), id.blinding_pubkey.end());
data.insert(data.end(), id.begin(), id.end());
return EncodeBase58Check(data);
}
CChainParams::Base58Type type = for_parent ? CChainParams::PARENT_PUBKEY_ADDRESS : CChainParams::PUBKEY_ADDRESS;
std::vector<unsigned char> data = m_params.Base58Prefix(type);
data.insert(data.end(), id.begin(), id.end());
return EncodeBase58Check(data);
}
std::string operator()(const CScriptID& id) const
std::string operator()(const ScriptHash& id) const
{
if (id.blinding_pubkey.IsFullyValid()) {
assert(!for_parent);
std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::BLINDED_ADDRESS);
// Blinded addresses have the actual address type prefix inside the payload.
std::vector<unsigned char> prefix = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS);
data.insert(data.end(), prefix.begin(), prefix.end());
data.insert(data.end(), id.blinding_pubkey.begin(), id.blinding_pubkey.end());
data.insert(data.end(), id.begin(), id.end());
return EncodeBase58Check(data);
}
CChainParams::Base58Type type = for_parent ? CChainParams::PARENT_SCRIPT_ADDRESS : CChainParams::SCRIPT_ADDRESS;
std::vector<unsigned char> data = m_params.Base58Prefix(type);
data.insert(data.end(), id.begin(), id.end());
@ -48,7 +71,15 @@ public:
std::string operator()(const WitnessV0KeyHash& id) const
{
std::vector<unsigned char> data = {0};
data.reserve(33);
data.reserve(53);
if (id.blinding_pubkey.IsFullyValid()) {
std::vector<unsigned char> bytes(id.blinding_pubkey.begin(), id.blinding_pubkey.end());
bytes.insert(bytes.end(), id.begin(), id.end());
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, bytes.begin(), bytes.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
}
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end());
const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
@ -58,6 +89,14 @@ public:
{
std::vector<unsigned char> data = {0};
data.reserve(53);
if (id.blinding_pubkey.IsFullyValid()) {
std::vector<unsigned char> bytes(id.blinding_pubkey.begin(), id.blinding_pubkey.end());
bytes.insert(bytes.end(), id.begin(), id.end());
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, bytes.begin(), bytes.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
}
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end());
const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
@ -70,6 +109,14 @@ public:
}
std::vector<unsigned char> data = {(unsigned char)id.version};
data.reserve(1 + (id.length * 8 + 4) / 5);
if (id.blinding_pubkey.IsFullyValid()) {
std::vector<unsigned char> bytes(id.blinding_pubkey.begin(), id.blinding_pubkey.end());
bytes.insert(bytes.end(), id.program, id.program + id.length);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, bytes.begin(), bytes.end());
const std::string& hrp = for_parent ? m_params.ParentBlech32HRP() : m_params.Blech32HRP();
return blech32::Encode(hrp, data);
}
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length);
const std::string& hrp = for_parent ? m_params.ParentBech32HRP() : m_params.Bech32HRP();
return bech32::Encode(hrp, data);
@ -82,24 +129,46 @@ public:
CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, const bool for_parent)
{
std::vector<unsigned char> data;
size_t pk_size = CPubKey::COMPRESSED_PUBLIC_KEY_SIZE;
uint160 hash;
if (DecodeBase58Check(str, data)) {
// base58-encoded Bitcoin addresses.
// Public-key-hash-addresses have version 0 (or 111 testnet).
// The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key.
// Blinded addresses have two prefixes: first the blinded one, then the traditional one.
const std::vector<unsigned char>& blinded_prefix = params.Base58Prefix(CChainParams::BLINDED_ADDRESS);
CChainParams::Base58Type type_pkh = for_parent ? CChainParams::PARENT_PUBKEY_ADDRESS : CChainParams::PUBKEY_ADDRESS;
const std::vector<unsigned char>& pubkey_prefix = params.Base58Prefix(type_pkh);
if (data.size() == hash.size() + pubkey_prefix.size() && std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), data.begin())) {
std::copy(data.begin() + pubkey_prefix.size(), data.end(), hash.begin());
return CKeyID(hash);
return PKHash(hash);
} else if (data.size() == hash.size() + blinded_prefix.size() + pubkey_prefix.size() + pk_size &&
std::equal(blinded_prefix.begin(), blinded_prefix.end(), data.begin()) &&
std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), data.begin() + blinded_prefix.size())) {
auto payload_start = data.begin() + blinded_prefix.size() + pubkey_prefix.size();
CPubKey pubkey;
pubkey.Set(payload_start, payload_start + pk_size);
std::copy(payload_start + pk_size, data.end(), hash.begin());
return PKHash(hash, pubkey);
}
// Script-hash-addresses have version 5 (or 196 testnet).
// The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script.
CChainParams::Base58Type type_sh = for_parent ? CChainParams::PARENT_SCRIPT_ADDRESS : CChainParams::SCRIPT_ADDRESS;
const std::vector<unsigned char>& script_prefix = params.Base58Prefix(type_sh);
if (data.size() == hash.size() + script_prefix.size() && std::equal(script_prefix.begin(), script_prefix.end(), data.begin())) {
std::copy(data.begin() + script_prefix.size(), data.end(), hash.begin());
return CScriptID(hash);
return ScriptHash(hash);
} else if (data.size() == hash.size() + blinded_prefix.size() + pubkey_prefix.size() + pk_size &&
std::equal(blinded_prefix.begin(), blinded_prefix.end(), data.begin()) &&
std::equal(script_prefix.begin(), script_prefix.end(), data.begin() + blinded_prefix.size())) {
auto payload_start = data.begin() + blinded_prefix.size() + script_prefix.size();
CPubKey pubkey;
pubkey.Set(payload_start, payload_start + pk_size);
std::copy(payload_start + pk_size, data.end(), hash.begin());
return ScriptHash(hash, pubkey);
}
}
data.clear();
@ -138,6 +207,56 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
return unk;
}
}
// ELEMENTS confidential addresses: version + 8to5(ecdhkey || witness program)
data.clear();
auto blech = blech32::Decode(str);
const std::string& bl_hrp = for_parent ? params.ParentBlech32HRP() : params.Blech32HRP();
if (blech.second.size() > 0 && blech.first == bl_hrp) {
// Blech32 decoding
int version = blech.second[0]; // The first 5 bit symbol is the witness version (0-16)
data.reserve(((blech.second.size() - 1) * 5) / 8);
// The rest of the symbols are converted blinding pubkey and witness program bytes.
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, blech.second.begin() + 1, blech.second.end())) {
// Must be long enough for blinding key and other data taken below
if (data.size() < 34) {
return CNoDestination();
}
std::vector<unsigned char> pubkey_bytes(data.begin(), data.begin()+33);
data = std::vector<unsigned char>(data.begin()+33, data.end());
CPubKey blinding_pubkey(pubkey_bytes);
if (version == 0) {
{
WitnessV0KeyHash keyid;
if (data.size() == keyid.size()) {
std::copy(data.begin(), data.end(), keyid.begin());
keyid.blinding_pubkey = blinding_pubkey;
return keyid;
}
}
{
WitnessV0ScriptHash scriptid;
if (data.size() == scriptid.size()) {
std::copy(data.begin(), data.end(), scriptid.begin());
scriptid.blinding_pubkey = blinding_pubkey;
return scriptid;
}
}
return CNoDestination();
}
if (version > 16 || data.size() < 2 || data.size() > 40) {
return CNoDestination();
}
WitnessUnknown unk;
unk.version = version;
std::copy(data.begin(), data.end(), unk.program);
unk.blinding_pubkey = blinding_pubkey;
unk.length = data.size();
return unk;
}
}
return CNoDestination();
}
} // namespace

View file

@ -178,16 +178,17 @@ CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest)
{
// Only supports destinations which map to single public keys, i.e. P2PKH,
// P2WPKH, and P2SH-P2WPKH.
if (auto id = boost::get<CKeyID>(&dest)) {
return *id;
if (auto id = boost::get<PKHash>(&dest)) {
return CKeyID(*id);
}
if (auto witness_id = boost::get<WitnessV0KeyHash>(&dest)) {
return CKeyID(*witness_id);
}
if (auto script_id = boost::get<CScriptID>(&dest)) {
if (auto script_hash = boost::get<ScriptHash>(&dest)) {
CScript script;
CScriptID script_id(*script_hash);
CTxDestination inner_dest;
if (store.GetCScript(*script_id, script) && ExtractDestination(script, inner_dest)) {
if (store.GetCScript(script_id, script) && ExtractDestination(script, inner_dest)) {
if (auto inner_witness_id = boost::get<WitnessV0KeyHash>(&inner_dest)) {
return CKeyID(*inner_witness_id);
}

View file

@ -124,7 +124,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
// Pad block weight to account for OP_RETURN commitments with two compressed pubkeys
for (const auto& commitment : commitments) {
CTxOut output(0, commitment);
CTxOut output(CAsset(), 0, commitment);
nBlockWeight += ::GetSerializeSize(output, PROTOCOL_VERSION)*WITNESS_SCALE_FACTOR;
}
// END PAK
@ -187,12 +187,23 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
coinbaseTx.vin[0].prevout.SetNull();
coinbaseTx.vout.resize(1);
coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
coinbaseTx.vout[0].nAsset = policyAsset;
coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
if (g_con_elementsmode) {
if(chainparams.GetConsensus().subsidy_asset != policyAsset) {
// Only claim the subsidy if it's the same as the policy asset.
coinbaseTx.vout[0].nValue = nFees;
}
// 0-value outputs must be unspendable
if (coinbaseTx.vout[0].nValue.GetAmount() == 0) {
coinbaseTx.vout[0].scriptPubKey = CScript() << OP_RETURN;
}
}
coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
// ELEMENTS: PAK
// Add PAK transition commitments
for (unsigned int i = 0; i < commitments.size(); i++) {
coinbaseTx.vout.push_back(CTxOut(0, commitments[i]));
coinbaseTx.vout.push_back(CTxOut(CAsset(), 0, commitments[i]));
}
// END PAK
pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));

View file

@ -45,14 +45,14 @@ const std::string& FormatOutputType(OutputType type)
CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
{
switch (type) {
case OutputType::LEGACY: return key.GetID();
case OutputType::LEGACY: return PKHash(key);
case OutputType::P2SH_SEGWIT:
case OutputType::BECH32: {
if (!key.IsCompressed()) return key.GetID();
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
if (!key.IsCompressed()) return PKHash(key);
CTxDestination witdest = WitnessV0KeyHash(PKHash(key));
CScript witprog = GetScriptForDestination(witdest);
if (type == OutputType::P2SH_SEGWIT) {
return CScriptID(witprog);
return ScriptHash(witprog);
} else {
return witdest;
}
@ -61,12 +61,33 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
}
}
// Elements
CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type, const CPubKey& blinding_pubkey)
{
switch (type) {
case OutputType::LEGACY: return PKHash(key, blinding_pubkey);
case OutputType::P2SH_SEGWIT:
case OutputType::BECH32: {
if (!key.IsCompressed()) return PKHash(key, blinding_pubkey);
CTxDestination witdest = WitnessV0KeyHash(PKHash(key), blinding_pubkey);
CScript witprog = GetScriptForDestination(witdest);
if (type == OutputType::P2SH_SEGWIT) {
return ScriptHash(witprog, blinding_pubkey);
} else {
return witdest;
}
}
default: assert(false);
}
}
//
std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key)
{
CKeyID keyid = key.GetID();
PKHash keyid(key);
if (key.IsCompressed()) {
CTxDestination segwit = WitnessV0KeyHash(keyid);
CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit));
CTxDestination p2sh = ScriptHash(GetScriptForDestination(segwit));
return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)};
} else {
return std::vector<CTxDestination>{std::move(keyid)};
@ -80,19 +101,19 @@ CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript&
// Note that scripts over 520 bytes are not yet supported.
switch (type) {
case OutputType::LEGACY:
return CScriptID(script);
return ScriptHash(script);
case OutputType::P2SH_SEGWIT:
case OutputType::BECH32: {
CTxDestination witdest = WitnessV0ScriptHash(script);
CScript witprog = GetScriptForDestination(witdest);
// Check if the resulting program is solvable (i.e. doesn't use an uncompressed key)
if (!IsSolvable(keystore, witprog)) return CScriptID(script);
if (!IsSolvable(keystore, witprog)) return ScriptHash(script);
// Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours.
keystore.AddCScript(witprog);
if (type == OutputType::BECH32) {
return witdest;
} else {
return CScriptID(witprog);
return ScriptHash(CScriptID(witprog));
}
}
default: assert(false);

View file

@ -34,6 +34,7 @@ const std::string& FormatOutputType(OutputType type);
* The caller must make sure LearnRelatedScripts has been called beforehand.
*/
CTxDestination GetDestinationForKey(const CPubKey& key, OutputType);
CTxDestination GetDestinationForKey(const CPubKey& key, OutputType, const CPubKey& blinding_pubkey);
/** Get all destinations (potentially) supported by the wallet for the given key. */
std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key);

View file

@ -54,18 +54,16 @@ bool GetAmountFromParentChainPegin(CAmount& amount, const Sidechain::Bitcoin::CT
bool GetAmountFromParentChainPegin(CAmount& amount, const CTransaction& txBTC, unsigned int nOut)
{
//if (!txBTC.vout[nOut].nValue.IsExplicit()) {
// return false;
//}
//if (!txBTC.vout[nOut].nAsset.IsExplicit()) {
// return false;
//}
//if (txBTC.vout[nOut].nAsset.GetAsset() != Params().GetConsensus().parent_pegged_asset) {
// return false;
//}
//amount = txBTC.vout[nOut].nValue.GetAmount();
//TODO(rebase) re-enable above for CA/CT
amount = txBTC.vout[nOut].nValue;
if (!txBTC.vout[nOut].nValue.IsExplicit()) {
return false;
}
if (!txBTC.vout[nOut].nAsset.IsExplicit()) {
return false;
}
if (txBTC.vout[nOut].nAsset.GetAsset() != Params().GetConsensus().parent_pegged_asset) {
return false;
}
amount = txBTC.vout[nOut].nValue.GetAmount();
return true;
}
@ -75,67 +73,72 @@ CScript calculate_contract(const CScript& federation_script, const CScript& scri
std::vector<std::vector<unsigned char> > solutions;
unsigned int required;
std::vector<std::vector<unsigned char>> keys;
bool is_liquidv1_watchman = MatchLiquidWatchman(federation_script);
// Sanity check federation_script only to match 3 templates
if (federation_script != CScript() << OP_TRUE &&
!MatchMultisig(federation_script, required, keys) &&
!MatchLiquidWatchman(federation_script)) {
assert(false);
if (!is_liquidv1_watchman &&
federation_script != CScript() << OP_TRUE &&
!MatchMultisig(federation_script, required, keys)) {
assert(false);
}
CScript::const_iterator sdpc = federation_script.begin();
std::vector<unsigned char> vch;
opcodetype opcodeTmp;
bool liquid_op_else_found = false;
while (federation_script.GetOp(sdpc, opcodeTmp, vch))
{
CScript::const_iterator sdpc = federation_script.begin();
std::vector<unsigned char> vch;
opcodetype opcodeTmp;
while (federation_script.GetOp(sdpc, opcodeTmp, vch))
// For liquid watchman template, don't tweak emergency keys
if (is_liquidv1_watchman && opcodeTmp == OP_ELSE) {
liquid_op_else_found = true;
}
size_t pub_len = 33;
if (vch.size() == pub_len && !liquid_op_else_found)
{
size_t pub_len = 33;
if (vch.size() == pub_len)
{
unsigned char tweak[32];
CHMAC_SHA256(vch.data(), pub_len).Write(scriptPubKey.data(), scriptPubKey.size()).Finalize(tweak);
int ret;
secp256k1_pubkey watchman;
secp256k1_pubkey tweaked;
ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_validation, &watchman, vch.data(), pub_len);
assert(ret == 1);
ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_validation, &tweaked, vch.data(), pub_len);
assert(ret == 1);
// If someone creates a tweak that makes this fail, they broke SHA256
ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx_validation, &tweaked, tweak);
assert(ret == 1);
unsigned char new_pub[33];
ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx_validation, new_pub, &pub_len, &tweaked, SECP256K1_EC_COMPRESSED);
assert(ret == 1);
assert(pub_len == 33);
unsigned char tweak[32];
CHMAC_SHA256(vch.data(), pub_len).Write(scriptPubKey.data(), scriptPubKey.size()).Finalize(tweak);
int ret;
secp256k1_pubkey watchman;
secp256k1_pubkey tweaked;
ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_validation, &watchman, vch.data(), pub_len);
assert(ret == 1);
ret = secp256k1_ec_pubkey_parse(secp256k1_ctx_validation, &tweaked, vch.data(), pub_len);
assert(ret == 1);
// If someone creates a tweak that makes this fail, they broke SHA256
ret = secp256k1_ec_pubkey_tweak_add(secp256k1_ctx_validation, &tweaked, tweak);
assert(ret == 1);
unsigned char new_pub[33];
ret = secp256k1_ec_pubkey_serialize(secp256k1_ctx_validation, new_pub, &pub_len, &tweaked, SECP256K1_EC_COMPRESSED);
assert(ret == 1);
assert(pub_len == 33);
// push tweaked pubkey
std::vector<unsigned char> pub_vec(new_pub, new_pub + pub_len);
scriptDestination << pub_vec;
// push tweaked pubkey
std::vector<unsigned char> pub_vec(new_pub, new_pub + pub_len);
scriptDestination << pub_vec;
// Sanity checks to reduce pegin risk. If the tweaked
// value flips a bit, we may lose pegin funds irretrievably.
// We take the tweak, derive its pubkey and check that
// `tweaked - watchman = tweak` to check the computation
// two different ways
secp256k1_pubkey tweaked2;
ret = secp256k1_ec_pubkey_create(secp256k1_ctx_validation, &tweaked2, tweak);
assert(ret);
ret = secp256k1_ec_pubkey_negate(secp256k1_ctx_validation, &watchman);
assert(ret);
secp256k1_pubkey* pubkey_combined[2];
pubkey_combined[0] = &watchman;
pubkey_combined[1] = &tweaked;
secp256k1_pubkey maybe_tweaked2;
ret = secp256k1_ec_pubkey_combine(secp256k1_ctx_validation, &maybe_tweaked2, pubkey_combined, 2);
assert(ret);
assert(!memcmp(&maybe_tweaked2, &tweaked2, 64));
// Sanity checks to reduce pegin risk. If the tweaked
// value flips a bit, we may lose pegin funds irretrievably.
// We take the tweak, derive its pubkey and check that
// `tweaked - watchman = tweak` to check the computation
// two different ways
secp256k1_pubkey tweaked2;
ret = secp256k1_ec_pubkey_create(secp256k1_ctx_validation, &tweaked2, tweak);
assert(ret);
ret = secp256k1_ec_pubkey_negate(secp256k1_ctx_validation, &watchman);
assert(ret);
secp256k1_pubkey* pubkey_combined[2];
pubkey_combined[0] = &watchman;
pubkey_combined[1] = &tweaked;
secp256k1_pubkey maybe_tweaked2;
ret = secp256k1_ec_pubkey_combine(secp256k1_ctx_validation, &maybe_tweaked2, pubkey_combined, 2);
assert(ret);
assert(!memcmp(&maybe_tweaked2, &tweaked2, 64));
} else {
// add to script untouched
if (vch.size() > 0) {
scriptDestination << vch;
} else {
// add to script untouched
if (vch.size() > 0) {
scriptDestination << vch;
} else {
scriptDestination << opcodeTmp;
}
scriptDestination << opcodeTmp;
}
}
}
@ -177,7 +180,7 @@ static bool CheckPeginTx(const std::vector<unsigned char>& tx_data, T& pegtx, co
// Check that the witness program matches the p2ch on the p2sh-p2wsh transaction output
CScript tweaked_fedpegscript = calculate_contract(Params().GetConsensus().fedpegScript, claim_script);
CScript witness_output(GetScriptForWitness(tweaked_fedpegscript));
CScript expected_script(CScript() << OP_HASH160 << ToByteVector(CScriptID(witness_output)) << OP_EQUAL);
CScript expected_script(CScript() << OP_HASH160 << ToByteVector(ScriptHash(CScriptID(witness_output))) << OP_EQUAL);
if (pegtx->vout[prevout.n].scriptPubKey != expected_script) {
return false;
}
@ -273,8 +276,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
err_msg = "Asset type was not 32 bytes.";
return false;
}
//TODO(rebase) CA
//CAsset asset(stack[1]);
CAsset asset(stack[1]);
// Get genesis blockhash
if (stack[2].size() != 32) {
@ -345,11 +347,10 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
return false;
}
//TODO(rebase) CA
//// Check the asset type corresponds to a valid pegged asset (only one for now)
//if (asset != Params().GetConsensus().pegged_asset) {
// return false;
//}
// Check the asset type corresponds to a valid pegged asset (only one for now)
if (asset != Params().GetConsensus().pegged_asset) {
return false;
}
// Finally, validate peg-in via rpc call
if (check_depth && gArgs.GetBoolArg("-validatepegin", DEFAULT_VALIDATE_PEGIN)) {
@ -367,9 +368,7 @@ CTxOut GetPeginOutputFromWitness(const CScriptWitness& pegin_witness) {
CAmount value;
stream >> value;
//TODO(rebase) CA
//return CTxOut(CAsset(pegin_witness.stack[1]), value, CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end()));
return CTxOut(value, CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end()));
return CTxOut(CAsset(pegin_witness.stack[1]), CConfidentialValue(value), CScript(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end()));
}
bool MatchLiquidWatchman(const CScript& script)
@ -441,5 +440,5 @@ bool MatchLiquidWatchman(const CScript& script)
return false;
}
// No more pushes
return (it + 1 == script.end());
return (it == script.end());
}

View file

@ -16,6 +16,8 @@
#include <utilstrencodings.h>
#include <chainparams.h> // Peg-out enforcement
// ELEMENTS:
CAsset policyAsset;
CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
{
@ -53,7 +55,13 @@ CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
{
return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn));
if (!txout.nValue.IsExplicit())
return false; // FIXME
if (!txout.nAsset.IsExplicit())
return false;
if (txout.IsFee())
return false;
return (txout.nValue.GetAmount() < GetDustThreshold(txout, dustRelayFeeIn));
}
bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType)
@ -129,21 +137,19 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason)
return false;
}
if (whichType == TX_NULL_DATA) {
}
if (whichType == TX_NULL_DATA) {
nDataOut++;
if (params.GetEnforcePak() &&
txout.scriptPubKey.IsPegoutScript(params.ParentGenesisBlockHash()) &&
(!ScriptHasValidPAKProof(txout.scriptPubKey, params.ParentGenesisBlockHash()))) {
// TODO(rebase) CT/CA check for asset type
txout.nAsset.IsExplicit() && txout.nAsset.GetAsset() == Params().GetConsensus().pegged_asset &&
(!ScriptHasValidPAKProof(txout.scriptPubKey, params.ParentGenesisBlockHash()))) {
reason = "invalid-pegout-proof";
return false;
}
} else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) {
reason = "bare-multisig";
return false;
} else if (IsDust(txout, ::dustRelayFee)) {
} else if ((txout.nAsset.IsExplicit() && txout.nAsset.GetAsset() == policyAsset) && IsDust(txout, ::dustRelayFee)) {
reason = "dust";
return false;
}

View file

@ -16,6 +16,9 @@
class CCoinsViewCache;
class CTxOut;
// ELEMENTS:
extern CAsset policyAsset;
/** Default for -blockmaxweight, which controls the range of block weights the mining code will create **/
static const unsigned int DEFAULT_BLOCK_MAX_WEIGHT = MAX_BLOCK_WEIGHT - 4000;
/** Default for -blockmintxfee, which sets the minimum feerate for a transaction in blocks created by mining code **/

View file

@ -0,0 +1,34 @@
#include <primitives/confidential.h>
#include <tinyformat.h>
void CConfidentialAsset::SetToAsset(const CAsset& asset)
{
vchCommitment.clear();
vchCommitment.reserve(nExplicitSize);
vchCommitment.push_back(1);
vchCommitment.insert(vchCommitment.end(), asset.begin(), asset.end());
}
void CConfidentialValue::SetToAmount(const CAmount amount)
{
vchCommitment.resize(nExplicitSize);
vchCommitment[0] = 1;
WriteBE64(&vchCommitment[1], amount);
}
std::string CAssetIssuance::ToString() const
{
std::string str;
str += "CAssetIssuance(";
str += assetBlindingNonce.ToString();
str += ", ";
str += assetEntropy.ToString();
if (!nAmount.IsNull())
str += strprintf(", amount=%s", (nAmount.IsExplicit() ? strprintf("%d.%08d", nAmount.GetAmount() / COIN, nAmount.GetAmount() % COIN) : std::string("CONFIDENTIAL")));
if (!nInflationKeys.IsNull())
str += strprintf(", inflationkeys=%s", (nInflationKeys.IsExplicit() ? strprintf("%d.%08d", nInflationKeys.GetAmount() / COIN, nInflationKeys.GetAmount() % COIN) : std::string("CONFIDENTIAL")));
str += ")";
return str;
}

View file

@ -0,0 +1,224 @@
#ifndef BITCOIN_PRIMITIVES_CONFIDENTIAL_H
#define BITCOIN_PRIMITIVES_CONFIDENTIAL_H
#include <amount.h>
#include <asset.h>
#include <script/script.h>
#include <serialize.h>
#include <span.h>
#include <uint256.h>
#include <utilstrencodings.h>
extern bool g_con_elementsmode;
/**
* Confidential assets, values, and nonces all share enough code in common
* that it makes sense to define a common abstract base class. */
template<size_t ExplicitSize, unsigned char PrefixA, unsigned char PrefixB>
class CConfidentialCommitment
{
public:
static const size_t nExplicitSize = ExplicitSize;
static const size_t nCommittedSize = 33;
std::vector<unsigned char> vchCommitment;
CConfidentialCommitment() { SetNull(); }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
unsigned char version = vchCommitment.empty()? 0: vchCommitment[0];
READWRITE(version);
if (ser_action.ForRead()) {
switch (version) {
/* Null */
case 0:
vchCommitment.clear();
return;
/* Explicit value */
case 1:
vchCommitment.resize(nExplicitSize);
break;
/* Confidential commitment */
case PrefixA:
case PrefixB:
vchCommitment.resize(nCommittedSize);
break;
/* Invalid serialization! */
default:
throw std::ios_base::failure("Unrecognized serialization prefix");
}
vchCommitment[0] = version;
}
if (vchCommitment.size() > 1) {
READWRITE(REF(Span<unsigned char>(vchCommitment.data() + 1, vchCommitment.size()-1)));
}
}
/* Null is the default state when no explicit asset or confidential
* asset commitment has been set. */
bool IsNull() const { return vchCommitment.empty(); }
void SetNull() { vchCommitment.clear(); }
bool IsExplicit() const
{
return vchCommitment.size()==nExplicitSize && vchCommitment[0]==1;
}
bool IsCommitment() const
{
return vchCommitment.size()==nCommittedSize && (vchCommitment[0]==PrefixA || vchCommitment[0]==PrefixB);
}
bool IsValid() const
{
return IsNull() || IsExplicit() || IsCommitment();
}
std::string GetHex() const { return HexStr(vchCommitment); }
friend bool operator==(const CConfidentialCommitment& a, const CConfidentialCommitment& b)
{
return a.vchCommitment == b.vchCommitment;
}
friend bool operator!=(const CConfidentialCommitment& a, const CConfidentialCommitment& b)
{
return !(a == b);
}
};
/** A commitment to a blinded asset, or an explicit asset NUMS identifier */
class CConfidentialAsset : public CConfidentialCommitment<33, 10, 11>
{
public:
CConfidentialAsset() {
SetNull();
}
CConfidentialAsset(CAsset asset) { SetToAsset(asset); }
void SetNull() {
vchCommitment.clear();
// Set to dummy asset when not doing CA.
if (!g_con_elementsmode) {
SetToAsset(CAsset());
}
}
/* An explicit asset identifier is a 256-bit nothing-up-my-sleeve number
* that used as auxiliary input to the Pedersen commitment setup to create
* a generator which acts as the asset tag. */
const CAsset& GetAsset() const
{
assert(IsExplicit());
return *reinterpret_cast<const CAsset*>(&vchCommitment[1]);
}
void SetToAsset(const CAsset& asset);
};
/** A 33-byte commitment to a confidential value, or a 64-bit explicit value. */
class CConfidentialValue : public CConfidentialCommitment<9, 8, 9>
{
public:
CConfidentialValue() { SetNull(); }
CConfidentialValue(CAmount nAmount) { SetToAmount(nAmount); }
/* An explicit value is called an amount. The first byte indicates it is
* an explicit value, and the remaining 8 bytes is the value serialized as
* a 64-bit big-endian integer. */
CAmount GetAmount() const
{
if (!g_con_elementsmode && IsNull()) {
return -1;
}
assert(IsExplicit());;
return ReadBE64(&vchCommitment[1]);
}
void SetToAmount(CAmount nAmount);
};
/**
* A 33-byte data field that typically is used to convey to the
* recipient the ECDH ephemeral key (an EC point) for deriving the
* transaction output blinding factor. */
class CConfidentialNonce : public CConfidentialCommitment<33, 2, 3>
{
public:
CConfidentialNonce() { SetNull(); }
};
/** A new asset issuance, or a reissuance (inflation) of an existing asset */
class CAssetIssuance
{
public:
// == 0
// Indicates new asset issuance.
// != 0
// This is a revelation of the blinding factor for the input,
// which shows that the input being spent is of the reissuance
// capability type for the asset being inflated.
uint256 assetBlindingNonce;
// New asset issuance:
// This is a 32-byte nonce of no consensus-defined meaning,
// but is used as additional entropy to the asset tag calculation.
// This is used by higher-layer protocols for defining the
// Ricardian contract governing the asset.
// Existing asset reissuance:
// The original asset entropy (combination of Ricardian contract
// and outpoint used) which was used to generate the fixed asset
// tag and reissuance tokens.
uint256 assetEntropy;
// Both explicit and blinded issuance amounts are supported
// (see class definition for CConfidentialValue for details).
CConfidentialValue nAmount;
// If nonzero, specifies the number of asset issuance tokens to
// generate. These tokens are made available to the outputs of the
// generating transaction.
CConfidentialValue nInflationKeys;
public:
CAssetIssuance()
{
SetNull();
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action)
{
READWRITE(assetBlindingNonce);
READWRITE(assetEntropy);
READWRITE(nAmount);
READWRITE(nInflationKeys);
}
void SetNull() { nAmount.SetNull(); nInflationKeys.SetNull(); }
bool IsNull() const { return (nAmount.IsNull() && nInflationKeys.IsNull()); }
friend bool operator==(const CAssetIssuance& a, const CAssetIssuance& b)
{
return a.assetBlindingNonce == b.assetBlindingNonce &&
a.assetEntropy == b.assetEntropy &&
a.nAmount == b.nAmount &&
a.nInflationKeys == b.nInflationKeys;
}
friend bool operator!=(const CAssetIssuance& a, const CAssetIssuance& b)
{
return !(a == b);
}
std::string ToString() const;
};
#endif // BITCOIN_PRIMITIVES_CONFIDENTIAL_H

View file

@ -9,7 +9,7 @@
#include <tinyformat.h>
#include <utilstrencodings.h>
bool g_con_elementswitness = false;
bool g_con_elementsmode = false;
std::string COutPoint::ToString() const
{
@ -41,19 +41,27 @@ std::string CTxIn::ToString() const
str += strprintf(", scriptSig=%s", HexStr(scriptSig).substr(0, 24));
if (nSequence != SEQUENCE_FINAL)
str += strprintf(", nSequence=%u", nSequence);
if (!assetIssuance.IsNull())
str += strprintf(", %s", assetIssuance.ToString());
str += ")";
return str;
}
CTxOut::CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn)
CTxOut::CTxOut(const CConfidentialAsset& nAssetIn, const CConfidentialValue& nValueIn, CScript scriptPubKeyIn)
{
nAsset = nAssetIn;
nValue = nValueIn;
scriptPubKey = scriptPubKeyIn;
}
std::string CTxOut::ToString() const
{
return strprintf("CTxOut(nValue=%d.%08d, scriptPubKey=%s)", nValue / COIN, nValue % COIN, HexStr(scriptPubKey).substr(0, 30));
std::string strAsset;
if (nAsset.IsExplicit())
strAsset = strprintf("nAsset=%s, ", nAsset.GetAsset().GetHex());
if (nAsset.IsCommitment())
strAsset = std::string("nAsset=CONFIDENTIAL, ");
return strprintf("CTxOut(%snValue=%s, scriptPubKey=%s)", strAsset, (nValue.IsExplicit() ? strprintf("%d.%08d", nValue.GetAmount() / COIN, nValue.GetAmount() % COIN) : std::string("CONFIDENTIAL")), HexStr(scriptPubKey).substr(0, 30));
}
CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), nLockTime(0) {}
@ -81,13 +89,14 @@ uint256 CTransaction::ComputeWitnessHash() const
// ELEMENTS ONLY
uint256 CTransaction::GetWitnessOnlyHash() const
{
assert(g_con_elementswitness);
assert(g_con_elementsmode);
std::vector<uint256> leaves;
leaves.reserve(std::max(vin.size(), vout.size()));
/* Inputs */
for (size_t i = 0; i < vin.size(); ++i) {
const CTxInWitness& txinwit = witness.vtxinwit.size() <= i || vin[i].prevout.IsNull() ? CTxInWitness() : witness.vtxinwit[i];
// Input has no witness OR is null input(coinbase)
const CTxInWitness& txinwit = (witness.vtxinwit.size() <= i || vin[i].prevout.IsNull()) ? CTxInWitness() : witness.vtxinwit[i];
leaves.push_back(txinwit.GetHash());
}
uint256 hashIn = ComputeFastMerkleRoot(leaves);
@ -112,15 +121,19 @@ CTransaction::CTransaction(const CMutableTransaction& tx) :
CTransaction::CTransaction(CMutableTransaction&& tx) :
vin(std::move(tx.vin)), vout(std::move(tx.vout)), nVersion(tx.nVersion), nLockTime(tx.nLockTime), witness(std::move(tx.witness)),hash{ComputeHash()}, m_witness_hash{ComputeWitnessHash()} {}
CAmount CTransaction::GetValueOut() const
{
CAmount nValueOut = 0;
CAmountMap CTransaction::GetValueOutMap() const {
CAmountMap values;
for (const auto& tx_out : vout) {
nValueOut += tx_out.nValue;
if (!MoneyRange(tx_out.nValue) || !MoneyRange(nValueOut))
throw std::runtime_error(std::string(__func__) + ": value out of range");
if (tx_out.nValue.IsExplicit() && tx_out.nAsset.IsExplicit()) {
CAmountMap m;
m[tx_out.nAsset.GetAsset()] = tx_out.nValue.GetAmount();
values += m;
if (!MoneyRange(tx_out.nValue.GetAmount()) || !MoneyRange(values[tx_out.nAsset.GetAsset()]))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
}
return nValueOut;
return values;
}
unsigned int CTransaction::GetTotalSize() const

View file

@ -11,13 +11,14 @@
#include <script/script.h>
#include <serialize.h>
#include <uint256.h>
#include <primitives/confidential.h>
#include <primitives/txwitness.h>
static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000;
// ELEMENTS:
// Globals to avoid circular dependencies.
extern bool g_con_elementswitness;
extern bool g_con_elementsmode;
/** An outpoint - a combination of a transaction hash and an index n into its vout */
class COutPoint
@ -31,8 +32,7 @@ public:
/* If this flag is set, the CTxIn including this COutPoint has a
* CAssetIssuance object. */
//TODO(rebase) CA
//static const uint32_t OUTPOINT_ISSUANCE_FLAG = (1 << 31);
static const uint32_t OUTPOINT_ISSUANCE_FLAG = (1 << 31);
/* If this flag is set, the CTxIn including this COutPoint
* is a peg-in input. */
@ -93,6 +93,8 @@ public:
//
// ELEMENTS:
CAssetIssuance assetIssuance;
/* If this is set to true, the input is interpreted as a
* peg-in claim and processed as such */
bool m_is_pegin = false;
@ -143,14 +145,13 @@ public:
//
// ELEMENTS:
//TODO(rebase) CA/CT
//bool fHasAssetIssuance;
bool fHasAssetIssuance;
COutPoint outpoint;
if (!ser_action.ForRead()) {
if (prevout.n == (uint32_t) -1) {
// Coinbase inputs do not have asset issuances attached
// to them.
// fHasAssetIssuance = false;
fHasAssetIssuance = false;
outpoint = prevout;
} else {
// The issuance and pegin bits can't be set as it is used to indicate
@ -160,15 +161,15 @@ public:
assert(!(prevout.n & ~COutPoint::OUTPOINT_INDEX_MASK));
// The assetIssuance object is used to represent both new
// asset generation and reissuance of existing asset types.
// fHasAssetIssuance = !assetIssuance.IsNull();
fHasAssetIssuance = !assetIssuance.IsNull();
// The mode is placed in the upper bits of the outpoint's
// index field. The IssuanceMode enum values are chosen to
// make this as simple as a bitwise-OR.
outpoint.hash = prevout.hash;
outpoint.n = prevout.n & COutPoint::OUTPOINT_INDEX_MASK;
// if (fHasAssetIssuance) {
// outpoint.n |= COutPoint::OUTPOINT_ISSUANCE_FLAG;
// }
if (fHasAssetIssuance) {
outpoint.n |= COutPoint::OUTPOINT_ISSUANCE_FLAG;
}
if (m_is_pegin) {
outpoint.n |= COutPoint::OUTPOINT_PEGIN_FLAG;
}
@ -180,13 +181,13 @@ public:
if (ser_action.ForRead()) {
if (outpoint.n == (uint32_t) -1) {
// No asset issuance for Coinbase inputs.
// fHasAssetIssuance = false;
fHasAssetIssuance = false;
prevout = outpoint;
m_is_pegin = false;
} else {
// The presence of the asset issuance object is indicated by
// a bit set in the outpoint index field.
// fHasAssetIssuance = !!(outpoint.n & COutPoint::OUTPOINT_ISSUANCE_FLAG);
fHasAssetIssuance = !!(outpoint.n & COutPoint::OUTPOINT_ISSUANCE_FLAG);
// The interpretation of this input as a peg-in is indicated by
// a bit set in the outpoint index field.
m_is_pegin = !!(outpoint.n & COutPoint::OUTPOINT_PEGIN_FLAG);
@ -204,13 +205,22 @@ public:
READWRITE(scriptSig);
READWRITE(nSequence);
// ELEMENTS:
// The asset fields are deserialized only if they are present.
if (fHasAssetIssuance) {
READWRITE(assetIssuance);
} else if (ser_action.ForRead()) {
assetIssuance.SetNull();
}
}
friend bool operator==(const CTxIn& a, const CTxIn& b)
{
return (a.prevout == b.prevout &&
a.scriptSig == b.scriptSig &&
a.nSequence == b.nSequence);
a.nSequence == b.nSequence &&
a.assetIssuance == b.assetIssuance);
}
friend bool operator!=(const CTxIn& a, const CTxIn& b)
@ -227,7 +237,9 @@ public:
class CTxOut
{
public:
CAmount nValue;
CConfidentialAsset nAsset;
CConfidentialValue nValue;
CConfidentialNonce nNonce;
CScript scriptPubKey;
CTxOut()
@ -235,30 +247,58 @@ public:
SetNull();
}
CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn);
CTxOut(const CConfidentialAsset& nAssetIn, const CConfidentialValue& nValueIn, CScript scriptPubKeyIn);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(nValue);
READWRITE(scriptPubKey);
if (g_con_elementsmode) {
READWRITE(nAsset);
READWRITE(nValue);
READWRITE(nNonce);
READWRITE(scriptPubKey);
} else {
CAmount value;
if (!ser_action.ForRead()) {
value = nValue.GetAmount();
}
READWRITE(value);
if (ser_action.ForRead()) {
nValue.SetToAmount(value);
}
READWRITE(scriptPubKey);
}
}
void SetNull()
{
nValue = -1;
nAsset.SetNull();
nValue.SetNull();
nNonce.SetNull();
scriptPubKey.clear();
}
bool IsNull() const
{
return (nValue == -1);
if (!g_con_elementsmode) {
// Ignore the asset and the nonce in compatibility mode.
return nValue.IsNull() && scriptPubKey.empty();
}
return nAsset.IsNull() && nValue.IsNull() && nNonce.IsNull() && scriptPubKey.empty();
}
bool IsFee() const {
return g_con_elementsmode && scriptPubKey == CScript()
&& nValue.IsExplicit() && nAsset.IsExplicit();
}
friend bool operator==(const CTxOut& a, const CTxOut& b)
{
return (a.nValue == b.nValue &&
return (a.nAsset == b.nAsset &&
a.nValue == b.nValue &&
a.nNonce == b.nNonce &&
a.scriptPubKey == b.scriptPubKey);
}
@ -300,7 +340,7 @@ inline void UnserializeTransaction(TxType& tx, Stream& s) {
// Witness serialization is different between Elements and Core.
// See code comments in SerializeTransaction for details about the differences.
if (g_con_elementswitness) {
if (g_con_elementsmode) {
s >> flags;
s >> tx.vin;
s >> tx.vout;
@ -368,7 +408,7 @@ inline void SerializeTransaction(const TxType& tx, Stream& s) {
}
// Witness serialization is different between Elements and Core.
if (g_con_elementswitness) {
if (g_con_elementsmode) {
// In Elements-style serialization, all normal data is serialized first and the
// witnesses all in the end.
s << flags;
@ -472,7 +512,7 @@ public:
uint256 GetWitnessOnlyHash() const;
// Return sum of txouts.
CAmount GetValueOut() const;
CAmountMap GetValueOutMap() const;
// GetValueIn() is a method on CCoinsViewCache, because
// inputs must be known to compute value in.

View file

@ -16,9 +16,8 @@
uint256 CTxInWitness::GetHash() const
{
std::vector<uint256> leaves;
//TODO(rebase) CA/CT
//leaves.push_back(SerializeHash(vchIssuanceAmountRangeproof, SER_GETHASH, 0));
//leaves.push_back(SerializeHash(vchInflationKeysRangeproof, SER_GETHASH, 0));
leaves.push_back(SerializeHash(vchIssuanceAmountRangeproof, SER_GETHASH, 0));
leaves.push_back(SerializeHash(vchInflationKeysRangeproof, SER_GETHASH, 0));
leaves.push_back(SerializeHash(scriptWitness.stack, SER_GETHASH, 0));
leaves.push_back(SerializeHash(m_pegin_witness.stack, SER_GETHASH, 0));
return ComputeFastMerkleRoot(leaves);

View file

@ -5,16 +5,15 @@
#ifndef BITCOIN_PRIMITIVES_TXWITNESS_H
#define BITCOIN_PRIMITIVES_TXWITNESS_H
#include <primitives/confidential.h>
#include <script/script.h>
#include <uint256.h>
class CTxInWitness
{
public:
// TODO generalize CScriptWitness into just CWitness
//TODO(rebase) CA/CT
//std::vector<unsigned char> vchIssuanceAmountRangeproof;
//std::vector<unsigned char> vchInflationKeysRangeproof;
std::vector<unsigned char> vchIssuanceAmountRangeproof;
std::vector<unsigned char> vchInflationKeysRangeproof;
CScriptWitness scriptWitness;
// Re-use script witness struct to include its own witness
CScriptWitness m_pegin_witness;
@ -24,8 +23,8 @@ public:
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action)
{
//READWRITE(vchIssuanceAmountRangeproof);
//READWRITE(vchInflationKeysRangeproof);
READWRITE(vchIssuanceAmountRangeproof);
READWRITE(vchInflationKeysRangeproof);
READWRITE(scriptWitness.stack);
READWRITE(m_pegin_witness.stack);
}
@ -34,13 +33,12 @@ public:
bool IsNull() const
{
//return vchIssuanceAmountRangeproof.empty() && vchInflationKeysRangeproof.empty() && scriptWitness.IsNull() && m_pegin_witness.IsNull();
return scriptWitness.IsNull() && m_pegin_witness.IsNull();
return vchIssuanceAmountRangeproof.empty() && vchInflationKeysRangeproof.empty() && scriptWitness.IsNull() && m_pegin_witness.IsNull();
}
void SetNull()
{
//vchIssuanceAmountRangeproof.clear();
//vchInflationKeysRangeproof.clear();
vchIssuanceAmountRangeproof.clear();
vchInflationKeysRangeproof.clear();
scriptWitness.stack.clear();
m_pegin_witness.stack.clear();
}
@ -107,15 +105,13 @@ public:
for (size_t n = 0; n < vtxinwit.size(); n++) {
READWRITE(vtxinwit[n]);
}
//TODO(rebase) CA/CT
//for (size_t n = 0; n < vtxoutwit.size(); n++) {
// READWRITE(vtxoutwit[n]);
//}
//TODO(stevenroose) re-enabled after testing
//if (IsNull()) {
// /* It's illegal to encode a witness when all vtxinwit and vtxoutwit entries are empty. */
// throw std::ios_base::failure("Superfluous witness record");
//}
for (size_t n = 0; n < vtxoutwit.size(); n++) {
READWRITE(vtxoutwit[n]);
}
if (IsNull()) {
/* It's illegal to encode a witness when all vtxinwit and vtxoutwit entries are empty. */
throw std::ios_base::failure("Superfluous witness record");
}
}
bool IsEmpty() const

View file

@ -419,7 +419,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
if (amount > 0)
{
CTxOut txout(amount, static_cast<CScript>(std::vector<unsigned char>(24, 0)));
CTxOut txout(::policyAsset, amount, static_cast<CScript>(std::vector<unsigned char>(24, 0)));
txDummy.vout.push_back(txout);
fDust |= IsDust(txout, model->node().getDustRelayFee());
}
@ -454,7 +454,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
nQuantity++;
// Amount
nAmount += out.txout.nValue;
nAmount += out.txout.nValue.GetAmount();
// Bytes
CTxDestination address;
@ -468,8 +468,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
else if(ExtractDestination(out.txout.scriptPubKey, address))
{
CPubKey pubkey;
CKeyID *keyid = boost::get<CKeyID>(&address);
if (keyid && model->wallet().getPubKey(*keyid, pubkey))
PKHash *keyid = boost::get<PKHash>(&address);
if (keyid && model->wallet().getPubKey(CKeyID(*keyid), pubkey))
{
nBytesInputs += (pubkey.IsCompressed() ? 148 : 180);
}
@ -510,7 +510,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
// Never create dust outputs; if we would, just add the dust to the fee.
if (nChange > 0 && nChange < MIN_CHANGE)
{
CTxOut txout(nChange, static_cast<CScript>(std::vector<unsigned char>(24, 0)));
CTxOut txout(::policyAsset, nChange, static_cast<CScript>(std::vector<unsigned char>(24, 0)));
if (IsDust(txout, model->node().getDustRelayFee()))
{
nPayFee += nChange;
@ -639,7 +639,7 @@ void CoinControlDialog::updateView()
for (const auto& outpair : coins.second) {
const COutPoint& output = std::get<0>(outpair);
const interfaces::WalletTxOut& out = std::get<1>(outpair);
nSum += out.txout.nValue;
nSum += out.txout.nValue.GetAmount();
nChildren++;
CCoinControlWidgetItem *itemOutput;
@ -676,8 +676,8 @@ void CoinControlDialog::updateView()
}
// amount
itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.txout.nValue));
itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)out.txout.nValue)); // padding so that sorting works correctly
itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.txout.nValue.GetAmount()));
itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)out.txout.nValue.GetAmount())); // padding so that sorting works correctly
// date
itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.time));

View file

@ -9,6 +9,7 @@
#include <qt/qvalidatedlineedit.h>
#include <qt/walletmodel.h>
#include <asset.h>
#include <base58.h>
#include <chainparams.h>
#include <primitives/transaction.h>
@ -211,7 +212,7 @@ bool isDust(interfaces::Node& node, const QString& address, const CAmount& amoun
{
CTxDestination dest = DecodeDestination(address.toStdString());
CScript script = GetScriptForDestination(dest);
CTxOut txOut(amount, script);
CTxOut txOut(CAsset(), amount, script);
return IsDust(txOut, node.getDustRelayFee());
}

View file

@ -15,6 +15,8 @@
#include <qt/transactiontablemodel.h>
#include <qt/walletmodel.h>
#include <policy/policy.h>
#include <QAbstractItemDelegate>
#include <QPainter>
@ -119,7 +121,7 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent)
{
ui->setupUi(this);
m_balances.balance = -1;
m_balances.balance[::policyAsset] = -1;
// use a SingleColorIcon for the "out of sync warning" icon
QIcon icon = platformStyle->SingleColorIcon(":/icons/warning");
@ -161,19 +163,19 @@ void OverviewPage::setBalance(const interfaces::WalletBalances& balances)
{
int unit = walletModel->getOptionsModel()->getDisplayUnit();
m_balances = balances;
ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balances.balance, false, BitcoinUnits::separatorAlways));
ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_balance, false, BitcoinUnits::separatorAlways));
ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_balance, false, BitcoinUnits::separatorAlways));
ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, false, BitcoinUnits::separatorAlways));
ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance, false, BitcoinUnits::separatorAlways));
ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_watch_only_balance, false, BitcoinUnits::separatorAlways));
ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, valueFor(balances.balance, ::policyAsset), false, BitcoinUnits::separatorAlways));
ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, valueFor(balances.unconfirmed_balance, ::policyAsset), false, BitcoinUnits::separatorAlways));
ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, valueFor(balances.immature_balance, ::policyAsset), false, BitcoinUnits::separatorAlways));
ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, valueFor(balances.balance, ::policyAsset) + valueFor(balances.unconfirmed_balance, ::policyAsset) + valueFor(balances.immature_balance, ::policyAsset), false, BitcoinUnits::separatorAlways));
ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, valueFor(balances.watch_only_balance, ::policyAsset), false, BitcoinUnits::separatorAlways));
ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, valueFor(balances.unconfirmed_watch_only_balance, ::policyAsset), false, BitcoinUnits::separatorAlways));
ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, valueFor(balances.immature_watch_only_balance, ::policyAsset), false, BitcoinUnits::separatorAlways));
ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, valueFor(balances.watch_only_balance, ::policyAsset) + valueFor(balances.unconfirmed_watch_only_balance, ::policyAsset) + valueFor(balances.immature_watch_only_balance, ::policyAsset), false, BitcoinUnits::separatorAlways));
// only show immature (newly mined) balance if it's non-zero, so as not to complicate things
// for the non-mining users
bool showImmature = balances.immature_balance != 0;
bool showWatchOnlyImmature = balances.immature_watch_only_balance != 0;
bool showImmature = valueFor(balances.immature_balance, ::policyAsset) != 0;
bool showWatchOnlyImmature = valueFor(balances.immature_watch_only_balance, ::policyAsset) != 0;
// for symmetry reasons also show immature label when the watch-only one is shown
ui->labelImmature->setVisible(showImmature || showWatchOnlyImmature);
@ -243,7 +245,7 @@ void OverviewPage::updateDisplayUnit()
{
if(walletModel && walletModel->getOptionsModel())
{
if (m_balances.balance != -1) {
if (m_balances.balance[::policyAsset] != -1) {
setBalance(m_balances);
}

View file

@ -566,7 +566,7 @@ bool PaymentServer::processPaymentRequest(const PaymentRequestPlus& request, Sen
}
// Extract and check amounts
CTxOut txOut(sendingTo.second, sendingTo.first);
CTxOut txOut(::policyAsset, sendingTo.second, sendingTo.first);
if (IsDust(txOut, optionsModel->node().getDustRelayFee())) {
Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
.arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),

View file

@ -21,6 +21,7 @@
#include <ui_interface.h>
#include <txmempool.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <wallet/fees.h>
#include <QFontMetrics>
@ -526,7 +527,7 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
{
if(model && model->getOptionsModel())
{
ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balances.balance));
ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), valueFor(balances.balance, ::policyAsset)));
}
}
@ -618,7 +619,7 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
}
// Calculate available amount to send.
CAmount amount = model->wallet().getAvailableBalance(coin_control);
CAmount amount = valueFor(model->wallet().getAvailableBalance(coin_control), ::policyAsset);
for (int i = 0; i < ui->entries->count(); ++i) {
SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
if (e && !e->isHidden() && e != entry) {
@ -784,7 +785,8 @@ void SendCoinsDialog::coinControlChangeChecked(int state)
{
if (state == Qt::Unchecked)
{
CoinControlDialog::coinControl()->destChange = CNoDestination();
// ELEMENTS: it's a map now, should be initialized automatically
//CoinControlDialog::coinControl()->destChange = CNoDestination();
ui->labelCoinControlChangeLabel->clear();
}
else
@ -800,7 +802,8 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text)
if (model && model->getAddressTableModel())
{
// Default to no change address until verified
CoinControlDialog::coinControl()->destChange = CNoDestination();
// ELEMENTS: it's a map now, should be initialized automatically
//CoinControlDialog::coinControl()->destChange = CNoDestination();
ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
const CTxDestination dest = DecodeDestination(text.toStdString());
@ -822,8 +825,10 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text)
QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm custom change address"), tr("The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?"),
QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
if(btnRetVal == QMessageBox::Yes)
CoinControlDialog::coinControl()->destChange = dest;
if(btnRetVal == QMessageBox::Yes) {
// ELEMENTS: it's a map now
//CoinControlDialog::coinControl()->destChange = dest;
}
else
{
ui->lineEditCoinControlChange->setText("");
@ -842,7 +847,8 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text)
else
ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
CoinControlDialog::coinControl()->destChange = dest;
// ELEMENTS: it's a map now
//CoinControlDialog::coinControl()->destChange = dest;
}
}
}

View file

@ -120,7 +120,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
ui->statusLabel_SM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again."));
return;
}
const CKeyID* keyID = boost::get<CKeyID>(&destination);
const PKHash* keyID = boost::get<PKHash>(&destination);
if (!keyID) {
ui->addressIn_SM->setValid(false);
ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }");
@ -137,7 +137,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
}
CKey key;
if (!model->wallet().getPrivKey(*keyID, key))
if (!model->wallet().getPrivKey(CKeyID(*keyID), key))
{
ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_SM->setText(tr("Private key for the entered address is not available."));
@ -198,7 +198,7 @@ void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
ui->statusLabel_VM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again."));
return;
}
if (!boost::get<CKeyID>(&destination)) {
if (!boost::get<PKHash>(&destination)) {
ui->addressIn_VM->setValid(false);
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_VM->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again."));
@ -229,7 +229,7 @@ void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
return;
}
if (!(CTxDestination(pubkey.GetID()) == destination)) {
if (!(CTxDestination(PKHash(pubkey.GetID())) == destination)) {
ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>"));
return;

View file

@ -13,6 +13,7 @@
#include <qt/transactionview.h>
#include <qt/walletmodel.h>
#include <key_io.h>
#include <policy/policy.h>
#include <test/test_bitcoin.h>
#include <validation.h>
#include <wallet/wallet.h>
@ -164,8 +165,8 @@ void TestGUI()
// Send two transactions, and verify they are added to transaction list.
TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
QCOMPARE(transactionTableModel->rowCount({}), 105);
uint256 txid1 = SendCoins(*wallet.get(), sendCoinsDialog, CKeyID(), 5 * COIN, false /* rbf */);
uint256 txid2 = SendCoins(*wallet.get(), sendCoinsDialog, CKeyID(), 10 * COIN, true /* rbf */);
uint256 txid1 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, false /* rbf */);
uint256 txid2 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 10 * COIN, true /* rbf */);
QCOMPARE(transactionTableModel->rowCount({}), 107);
QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
@ -182,7 +183,7 @@ void TestGUI()
QLabel* balanceLabel = overviewPage.findChild<QLabel*>("labelBalance");
QString balanceText = balanceLabel->text();
int unit = walletModel.getOptionsModel()->getDisplayUnit();
CAmount balance = walletModel.wallet().getBalance();
CAmount balance = valueFor(walletModel.wallet().getBalance(), ::policyAsset);
QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::separatorAlways);
QCOMPARE(balanceText, balanceComparison);

View file

@ -61,8 +61,8 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
int64_t nTime = wtx.time;
CAmount nCredit = wtx.credit;
CAmount nDebit = wtx.debit;
CAmount nCredit = valueFor(wtx.credit, ::policyAsset);
CAmount nDebit = valueFor(wtx.debit, ::policyAsset);
CAmount nNet = nCredit - nDebit;
strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(wtx, status, inMempool, numBlocks, adjustedTime);
@ -134,7 +134,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
//
CAmount nUnmatured = 0;
for (const CTxOut& txout : wtx.tx->vout)
nUnmatured += wallet.getCredit(txout, ISMINE_ALL);
nUnmatured += valueFor(wallet.getCredit(txout, ISMINE_ALL), ::policyAsset);
strHTML += "<b>" + tr("Credit") + ":</b> ";
if (status.is_in_main_chain)
strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", status.blocks_to_maturity) + ")";
@ -199,21 +199,21 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
}
}
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue.GetAmount()) + "<br>";
if(toSelf)
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue.GetAmount()) + "<br>";
}
if (fAllToMe)
{
// Payment to self
CAmount nChange = wtx.change;
CAmount nChange = valueFor(wtx.change, ::policyAsset);
CAmount nValue = nCredit - nChange;
strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
}
CAmount nTxFee = nDebit - wtx.tx->GetValueOut();
CAmount nTxFee = nDebit - valueFor(wtx.tx->GetValueOutMap(), ::policyAsset);
if (nTxFee > 0)
strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
}
@ -225,13 +225,13 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
auto mine = wtx.txin_is_mine.begin();
for (const CTxIn& txin : wtx.tx->vin) {
if (*(mine++)) {
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -valueFor(wallet.getDebit(txin, ISMINE_ALL), ::policyAsset)) + "<br>";
}
}
mine = wtx.txout_is_mine.begin();
for (const CTxOut& txout : wtx.tx->vout) {
if (*(mine++)) {
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout, ISMINE_ALL)) + "<br>";
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, valueFor(wallet.getCredit(txout, ISMINE_ALL), ::policyAsset)) + "<br>";
}
}
}
@ -286,10 +286,10 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
for (const CTxIn& txin : wtx.tx->vin)
if(wallet.txinIsMine(txin))
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet.getDebit(txin, ISMINE_ALL)) + "<br>";
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -valueFor(wallet.getDebit(txin, ISMINE_ALL), ::policyAsset)) + "<br>";
for (const CTxOut& txout : wtx.tx->vout)
if(wallet.txoutIsMine(txout))
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet.getCredit(txout, ISMINE_ALL)) + "<br>";
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, valueFor(wallet.getCredit(txout, ISMINE_ALL), ::policyAsset)) + "<br>";
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true);
@ -315,7 +315,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall
strHTML += GUIUtil::HtmlEscape(name) + " ";
strHTML += QString::fromStdString(EncodeDestination(address));
}
strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue.GetAmount());
strHTML = strHTML + " IsMine=" + (wallet.txoutIsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
strHTML = strHTML + " IsWatchOnly=" + (wallet.txoutIsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
}

View file

@ -7,6 +7,7 @@
#include <consensus/consensus.h>
#include <interfaces/wallet.h>
#include <key_io.h>
#include <policy/policy.h>
#include <timedata.h>
#include <validation.h>
@ -29,8 +30,8 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
{
QList<TransactionRecord> parts;
int64_t nTime = wtx.time;
CAmount nCredit = wtx.credit;
CAmount nDebit = wtx.debit;
CAmount nCredit = valueFor(wtx.credit, ::policyAsset);
CAmount nDebit = valueFor(wtx.debit, ::policyAsset);
CAmount nNet = nCredit - nDebit;
uint256 hash = wtx.tx->GetHash();
std::map<std::string, std::string> mapValue = wtx.value_map;
@ -49,7 +50,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
TransactionRecord sub(hash, nTime);
CTxDestination address;
sub.idx = i; // vout index
sub.credit = txout.nValue;
sub.credit = txout.nValue.GetAmount();
sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY;
if (wtx.txout_address_is_mine[i])
{
@ -93,7 +94,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
if (fAllFromMe && fAllToMe)
{
// Payment to self
CAmount nChange = wtx.change;
CAmount nChange = valueFor(wtx.change, ::policyAsset);
parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "",
-(nDebit - nChange), nCredit - nChange));
@ -104,7 +105,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
//
// Debit
//
CAmount nTxFee = nDebit - wtx.tx->GetValueOut();
CAmount nTxFee = nDebit - wtx.tx->GetValueOutMap()[::policyAsset];
for (unsigned int nOut = 0; nOut < wtx.tx->vout.size(); nOut++)
{
@ -133,7 +134,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface
sub.address = mapValue["to"];
}
CAmount nValue = txout.nValue;
CAmount nValue = txout.nValue.GetAmount();
/* Add fee to first output */
if (nTxFee > 0)
{

View file

@ -15,6 +15,7 @@
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <key_io.h>
#include <policy/policy.h>
#include <ui_interface.h>
#include <util.h> // for GetBoolArg
#include <wallet/coincontrol.h>
@ -154,7 +155,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
CScript scriptPubKey(scriptStr, scriptStr+out.script().size());
CAmount nAmount = out.amount();
CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount};
CRecipient recipient = {scriptPubKey, nAmount, ::policyAsset, CPubKey(), rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);
}
if (subtotal <= 0)
@ -177,7 +178,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
++nAddresses;
CScript scriptPubKey = GetScriptForDestination(DecodeDestination(rcp.address.toStdString()));
CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount};
CRecipient recipient = {scriptPubKey, rcp.amount, ::policyAsset, CPubKey(), rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);
total += rcp.amount;
@ -188,7 +189,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
return DuplicateAddress;
}
CAmount nBalance = m_wallet->getAvailableBalance(coinControl);
CAmount nBalance = m_wallet->getAvailableBalance(coinControl)[::policyAsset];
if(total > nBalance)
{

View file

@ -56,7 +56,7 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet)
if (out.amount() <= 0) continue;
if (i == nChangePosRet)
i++;
subtotal += walletTransaction->vout[i].nValue;
subtotal += walletTransaction->vout[i].nValue.GetAmount();
i++;
}
rcp.amount = subtotal;
@ -65,7 +65,7 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet)
{
if (i == nChangePosRet)
i++;
rcp.amount = walletTransaction->vout[i].nValue;
rcp.amount = walletTransaction->vout[i].nValue.GetAmount();
i++;
}
}

View file

@ -150,4 +150,29 @@ bool Random_SanityCheck();
/** Initialize the RNG. */
void RandomInit();
/** Shuffle function Poached from upstream: #14624 **/
/** More efficient than using std::shuffle on a FastRandomContext.
*
* This is more efficient as std::shuffle will consume entropy in groups of
* 64 bits at the time and throw away most.
*
* This also works around a bug in libstdc++ std::shuffle that may cause
* type::operator=(type&&) to be invoked on itself, which the library's
* debug mode detects and panics on. This is a known issue, see
* https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle
*/
template<typename I, typename R>
void Shuffle(I first, I last, R&& rng)
{
while (first != last) {
size_t j = rng.randrange(last - first);
if (j) {
using std::swap;
swap(*first, *(first + j));
}
++first;
}
}
#endif // BITCOIN_RANDOM_H

View file

@ -555,7 +555,9 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
for (const CCoin& coin : outs) {
UniValue utxo(UniValue::VOBJ);
utxo.pushKV("height", (int32_t)coin.nHeight);
utxo.pushKV("value", ValueFromAmount(coin.out.nValue));
if (coin.out.nValue.IsExplicit()) {
utxo.pushKV("value", ValueFromAmount(coin.out.nValue.GetAmount()));
}
// include the script in a json output
UniValue o(UniValue::VOBJ);

View file

@ -889,9 +889,13 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash,
for (const auto& output : outputs) {
ss << VARINT(output.first + 1);
ss << output.second.out.scriptPubKey;
ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
ss << output.second.out.nValue;
ss << output.second.out.nAsset;
ss << output.second.out.nNonce;
stats.nTransactionOutputs++;
stats.nTotalAmount += output.second.out.nValue;
if (output.second.out.nValue.IsExplicit()) {
stats.nTotalAmount += output.second.out.nValue.GetAmount();
}
stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */;
}
@ -1098,7 +1102,20 @@ UniValue gettxout(const JSONRPCRequest& request)
} else {
ret.pushKV("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1));
}
ret.pushKV("value", ValueFromAmount(coin.out.nValue));
if (coin.out.nValue.IsExplicit()) {
ret.pushKV("value", ValueFromAmount(coin.out.nValue.GetAmount()));
} else {
ret.pushKV("valuecommitment", coin.out.nValue.GetHex());
}
if (g_con_elementsmode) {
if (coin.out.nAsset.IsExplicit()) {
ret.pushKV("asset", coin.out.nAsset.GetAsset().GetHex());
} else {
ret.pushKV("assetcommitment", coin.out.nAsset.GetHex());
}
ret.pushKV("commitmentnonce", coin.out.nNonce.GetHex());
}
UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
ret.pushKV("scriptPubKey", o);
@ -1819,6 +1836,9 @@ static UniValue getblockstats(const JSONRPCRequest& request)
}
}
// ELEMENTS:
const CAsset asset = policyAsset; // TODO Make configurable
const CBlock block = GetBlockChecked(pindex);
const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default)
@ -1857,10 +1877,25 @@ static UniValue getblockstats(const JSONRPCRequest& request)
outputs += tx->vout.size();
CAmount tx_total_out = 0;
if (loop_outputs) {
for (const CTxOut& out : tx->vout) {
tx_total_out += out.nValue;
utxo_size_inc += GetSerializeSize(out, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD;
// ELEMENTS:
CAmount elements_txfee = 0;
if (g_con_elementsmode) {
if (loop_outputs) {
for (const CTxOut& out : tx->vout) {
if (out.IsFee() && out.nAsset.GetAsset() == asset) {
elements_txfee += out.nValue.GetAmount();
}
if (out.nValue.IsExplicit() && out.nAsset.IsExplicit() && out.nAsset.GetAsset() == asset) {
tx_total_out += out.nValue.GetAmount();
}
}
}
} else {
if (loop_outputs) {
for (const CTxOut& out : tx->vout) {
tx_total_out += out.nValue.GetAmount();
utxo_size_inc += GetSerializeSize(out, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD;
}
}
}
@ -1900,7 +1935,7 @@ static UniValue getblockstats(const JSONRPCRequest& request)
if (!g_txindex) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more of the selected stats requires -txindex enabled");
}
CAmount tx_total_in = 0;
CAmount tx_total_in = 0; // Only needed for bitcoin serialization
for (const CTxIn& in : tx->vin) {
if (in.m_is_pegin) {
continue;
@ -1913,12 +1948,11 @@ static UniValue getblockstats(const JSONRPCRequest& request)
}
CTxOut prevoutput = tx_in->vout[in.prevout.n];
tx_total_in += prevoutput.nValue;
tx_total_in += g_con_elementsmode ? 0 : prevoutput.nValue.GetAmount();
utxo_size_inc -= GetSerializeSize(prevoutput, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD;
}
CAmount txfee = tx_total_in - tx_total_out;
CAmount txfee = g_con_elementsmode ? elements_txfee : (tx_total_in - tx_total_out);
assert(MoneyRange(txfee));
if (do_medianfee) {
fee_array.push_back(txfee);
@ -2114,10 +2148,11 @@ UniValue scantxoutset(const JSONRPCRequest& request)
" \"vout\": n, (numeric) the vout value\n"
" \"scriptPubKey\" : \"script\", (string) the script key\n"
" \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
" \"asset\" : \"asset\", (hex) The asset ID\n"
" \"height\" : n, (numeric) Height of the unspent transaction output\n"
" }\n"
" ,...], \n"
" \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
" \"total_unblinded_bitcoin_amount\" : x.xxx, (numeric) The total amount of all found unspent unblinded outputs in " + CURRENCY_UNIT + "\n"
"]\n"
);
@ -2201,24 +2236,57 @@ UniValue scantxoutset(const JSONRPCRequest& request)
result.pushKV("success", res);
result.pushKV("searched_items", count);
for (const auto& it : coins) {
const COutPoint& outpoint = it.first;
const Coin& coin = it.second;
const CTxOut& txo = coin.out;
input_txos.push_back(txo);
total_in += txo.nValue;
if (!g_con_elementsmode) {
for (const auto& it : coins) {
const COutPoint& outpoint = it.first;
const Coin& coin = it.second;
const CTxOut& txo = coin.out;
input_txos.push_back(txo);
total_in += txo.nValue.GetAmount();
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.hash.GetHex());
unspent.pushKV("vout", (int32_t)outpoint.n);
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end()));
unspent.pushKV("amount", ValueFromAmount(txo.nValue));
unspent.pushKV("height", (int32_t)coin.nHeight);
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.hash.GetHex());
unspent.pushKV("vout", (int32_t)outpoint.n);
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end()));
unspent.pushKV("amount", ValueFromAmount(txo.nValue.GetAmount()));
unspent.pushKV("height", (int32_t)coin.nHeight);
unspents.push_back(unspent);
unspents.push_back(unspent);
}
result.pushKV("unspents", unspents);
result.pushKV("total_amount", ValueFromAmount(total_in));
} else {
CAmount total_in_explicit_parent = 0;
for (const auto& it : coins) {
const COutPoint& outpoint = it.first;
const Coin& coin = it.second;
const CTxOut& txo = coin.out;
input_txos.push_back(txo);
if (txo.nValue.IsExplicit() && txo.nAsset.IsExplicit() && txo.nAsset.GetAsset() == Params().GetConsensus().pegged_asset) {
total_in_explicit_parent += txo.nValue.GetAmount();
}
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.hash.GetHex());
unspent.pushKV("vout", (int32_t)outpoint.n);
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end()));
if (txo.nValue.IsExplicit()) {
unspent.pushKV("amount", ValueFromAmount(txo.nValue.GetAmount()));
} else {
unspent.pushKV("amountcommitment", HexStr(txo.nValue.vchCommitment));
}
if (txo.nAsset.IsExplicit()) {
unspent.pushKV("asset", txo.nAsset.GetAsset().GetHex());
} else {
unspent.pushKV("assetcommitment", HexStr(txo.nAsset.vchCommitment));
}
unspent.pushKV("height", (int32_t)coin.nHeight);
unspents.push_back(unspent);
}
result.pushKV("unspents", unspents);
result.pushKV("total_unblinded_bitcoin_amount", ValueFromAmount(total_in_explicit_parent));
}
result.pushKV("unspents", unspents);
result.pushKV("total_amount", ValueFromAmount(total_in));
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
}
@ -2258,8 +2326,7 @@ UniValue getsidechaininfo(const JSONRPCRequest& request)
UniValue obj(UniValue::VOBJ);
obj.pushKV("fedpegscript", HexStr(consensus.fedpegScript.begin(), consensus.fedpegScript.end()));
//TODO(rebase) CA
//obj.pushKV("pegged_asset", consensus.pegged_asset.GetHex());
obj.pushKV("pegged_asset", consensus.pegged_asset.GetHex());
obj.pushKV("min_peg_diff", consensus.parentChainPowLimit.GetHex());
obj.pushKV("parent_blockhash", parent_blockhash.GetHex());
obj.pushKV("parent_chain_has_pow", consensus.ParentChainHasPow());
@ -2267,8 +2334,7 @@ UniValue getsidechaininfo(const JSONRPCRequest& request)
if (!consensus.ParentChainHasPow()) {
obj.pushKV("parent_chain_signblockscript_asm", ScriptToAsmStr(consensus.parent_chain_signblockscript));
obj.pushKV("parent_chain_signblockscript_hex", HexStr(consensus.parent_chain_signblockscript));
//TODO(stevenroose) rebase CA
//obj.pushKV("parent_pegged_asset", HexStr(consensus.parent_pegged_asset));
obj.pushKV("parent_pegged_asset", HexStr(consensus.parent_pegged_asset));
}
return obj;
}

View file

@ -149,7 +149,6 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "logging", 1, "exclude" },
{ "disconnectnode", 1, "nodeid" },
{ "addwitnessaddress", 1, "p2sh" },
{ "initpegoutwallet", 1, "bip32_counter"},
// Echo with conversion (For testing only)
{ "echojson", 0, "arg0" },
{ "echojson", 1, "arg1" },
@ -161,12 +160,39 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "echojson", 7, "arg7" },
{ "echojson", 8, "arg8" },
{ "echojson", 9, "arg9" },
{ "rescanblockchain", 0, "start_height"},
{ "rescanblockchain", 1, "stop_height"},
{ "createwallet", 1, "disable_private_keys"},
{ "combineblocksigs", 1, "signatures"},
// ELEMENTS:
{ "rescanblockchain", 0, "start_height" },
{ "rescanblockchain", 1, "stop_height" },
{ "createwallet", 1, "disable_private_keys" },
{ "combineblocksigs", 1, "signatures" },
{ "sendtomainchain", 1, "amount" },
{ "sendtomainchain", 2, "subtractfeefromamount" },
{ "dumpissuanceblindingkey", 1, "vin" },
{ "importissuanceblindingkey", 1, "vin" },
{ "rawissueasset", 1, "issuances" },
{ "rawreissueasset", 1, "reissuances" },
{ "getnewblockhex", 0, "min_tx_age" },
{ "testproposedblock", 1, "acceptnonstd" },
{ "issueasset", 0, "assetamount" },
{ "issueasset", 1, "tokenamount" },
{ "issueasset", 2, "blind" },
{ "reissueasset", 1, "assetamount" },
{ "initpegoutwallet", 1, "bip32_counter"},
{ "rawblindrawtransaction", 1, "inputblinder" },
{ "rawblindrawtransaction", 2, "inputamount" },
{ "rawblindrawtransaction", 3, "inputasset" },
{ "rawblindrawtransaction", 4, "inputassetblinder" },
{ "rawblindrawtransaction", 6, "ignoreblindfail" },
{ "blindrawtransaction", 1, "ignoreblindfail" },
{ "blindrawtransaction", 2, "asset_commitments" },
{ "blindrawtransaction", 3, "blind_issuances" },
{ "destroyamount", 1, "amount" },
{ "sendmany", 8 , "output_assets" },
{ "sendmany", 9 , "ignoreblindfail" },
{ "sendtoaddress", 9 , "ignoreblindfail" },
{ "importblindingkey", 2, "key_is_master"},
{ "createrawtransaction", 4, "output_assets" },
};
// clang-format on

View file

@ -659,7 +659,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request)
result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex());
result.pushKV("transactions", transactions);
result.pushKV("coinbaseaux", aux);
result.pushKV("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue);
result.pushKV("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue.GetAmount());
result.pushKV("longpollid", chainActive.Tip()->GetBlockHash().GetHex() + i64tostr(nTransactionsUpdatedLast));
result.pushKV("target", hashTarget.GetHex());
result.pushKV("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1);
@ -981,7 +981,7 @@ UniValue getnewblockhex(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 1)
throw std::runtime_error(
"getnewblockhex\n"
"getnewblockhex ( min_tx_age )\n"
"\nGets hex representation of a proposed, unmined new block\n"
"\nArguments:\n"
"1. min_tx_age (numeric, optional, default=0) How many seconds a transaction must have been in the mempool to be inluded in the block proposal. This may help with faster block convergence among functionaries using compact blocks.\n"

View file

@ -30,6 +30,8 @@
#include <univalue.h>
#include <assetsdir.h>
static UniValue validateaddress(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
@ -52,6 +54,8 @@ static UniValue validateaddress(const JSONRPCRequest& request)
" \"iswitness\" : true|false, (boolean) If the address is a witness address\n"
" \"witness_version\" : version (numeric, optional) The version number of the witness program\n"
" \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n"
" \"confidential_key\" : \"hex\", (string) The hex value of the raw blinding public key for that address, if any. \"\" if none.\n"
" \"unconfidential\" : \"address\", (string) The address without confidentiality key.\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
@ -75,6 +79,8 @@ static UniValue validateaddress(const JSONRPCRequest& request)
UniValue detail = DescribeAddress(dest);
ret.pushKVs(detail);
UniValue blind_detail = DescribeBlindAddress(dest);
ret.pushKVs(blind_detail);
}
return ret;
}
@ -177,8 +183,8 @@ static UniValue verifymessage(const JSONRPCRequest& request)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
}
const CKeyID *keyID = boost::get<CKeyID>(&destination);
if (!keyID) {
const PKHash *pkhash = boost::get<PKHash>(&destination);
if (!pkhash) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
}
@ -196,7 +202,7 @@ static UniValue verifymessage(const JSONRPCRequest& request)
if (!pubkey.RecoverCompact(ss.GetHash(), vchSig))
return false;
return (pubkey.GetID() == *keyID);
return (pubkey.GetID() == *pkhash);
}
static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
@ -470,7 +476,7 @@ UniValue tweakfedpegscript(const JSONRPCRequest& request)
std::vector<unsigned char> scriptData = ParseHex(request.params[0].get_str());
CScript claim_script = CScript(scriptData.begin(), scriptData.end());
CScript tweaked_script = calculate_contract(Params().GetConsensus().fedpegScript, claim_script);
CTxDestination parent_addr(CScriptID(GetScriptForWitness(tweaked_script)));
CTxDestination parent_addr(ScriptHash(GetScriptForWitness(tweaked_script)));
UniValue ret(UniValue::VOBJ);
ret.pushKV("script", HexStr(tweaked_script));
@ -542,6 +548,103 @@ UniValue calcfastmerkleroot(const JSONRPCRequest& request)
return ret;
}
UniValue dumpassetlabels(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
"dumpassetlabels\n"
"\nLists all known asset id/label pairs in this wallet. This list can be modified with `-assetdir` configuration argument.\n"
+ HelpExampleCli("dumpassetlabels", "" )
+ HelpExampleRpc("dumpassetlabels", "" )
);
UniValue obj(UniValue::VOBJ);
for (const auto& as : gAssetsDir.GetKnownAssets()) {
obj.pushKV(gAssetsDir.GetLabel(as), as.GetHex());
}
return obj;
}
class BlindingPubkeyAdderVisitor : public boost::static_visitor<>
{
public:
CPubKey blind_key;
explicit BlindingPubkeyAdderVisitor(const CPubKey& blind_key_in) : blind_key(blind_key_in) {}
void operator()(const CNoDestination& dest) const {}
void operator()(PKHash& keyID) const
{
keyID.blinding_pubkey = blind_key;
}
void operator()(ScriptHash& scriptID) const
{
scriptID.blinding_pubkey = blind_key;
}
void operator()(WitnessV0KeyHash& id) const
{
id.blinding_pubkey = blind_key;
}
void operator()(WitnessV0ScriptHash& id) const
{
id.blinding_pubkey = blind_key;
}
void operator()(WitnessUnknown& id) const
{
id.blinding_pubkey = blind_key;
}
void operator()(const NullData& id) const {}
};
UniValue createblindedaddress(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 2)
{
throw std::runtime_error(
"createblindedaddress address blinding_key\n"
"\nCreates a blinded address using the provided blinding key.\n"
"\nArguments:\n"
"1. \"address\" (string, required) The unblinded address to be blinded.\n"
"2. \"blinding_key\" (string, required) The blinding public key. This can be obtained for a given address using `validateaddress`.\n"
"\nResult:\n"
"\"blinded_address\" (string) The blinded address.\n"
"\nExamples:\n"
"\nCreate a multisig address from 2 addresses\n"
+ HelpExampleCli("createblindedaddress", "HEZk3iQi1jC49bxUriTtynnXgWWWdAYx16 ec09811118b6febfa5ebe68642e5091c418fbace07e655da26b4a845a691fc2d") +
"\nAs a json rpc call\n"
+ HelpExampleRpc("createblindedaddress", "HEZk3iQi1jC49bxUriTtynnXgWWWdAYx16, ec09811118b6febfa5ebe68642e5091c418fbace07e655da26b4a845a691fc2d")
);
}
CTxDestination address = DecodeDestination(request.params[0].get_str());
if (!IsValidDestination(address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
}
if (IsBlindDestination(address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not an unblinded address");
}
if (!IsHex(request.params[1].get_str())) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal for key");
}
std::vector<unsigned char> keydata = ParseHex(request.params[1].get_str());
if (keydata.size() != 33) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length, must be length 66.");
}
CPubKey key;
key.Set(keydata.begin(), keydata.end());
// Append blinding key and return
boost::apply_visitor(BlindingPubkeyAdderVisitor(key), address);
return EncodeDestination(address);
}
// END ELEMENTS CALLS
//
@ -560,6 +663,8 @@ static const CRPCCommand commands[] =
// ELEMENTS:
{ "util", "getpakinfo", &getpakinfo, {}},
{ "util", "tweakfedpegscript", &tweakfedpegscript, {"claim_script"} },
{ "util", "createblindedaddress", &createblindedaddress, {"address", "blinding_key"}},
{ "util", "dumpassetlabels", &dumpassetlabels, {}},
{ "hidden", "calcfastmerkleroot", &calcfastmerkleroot, {"leaves"} },
/* Not shown in help */

View file

@ -3,6 +3,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <asset.h>
#include <chain.h>
#include <coins.h>
#include <compat/byteswap.h>
@ -27,7 +28,11 @@
#include <uint256.h>
#include <utilstrencodings.h>
#include <validation.h>
#include <confidential_validation.h>
#include <validationinterface.h>
#include <blind.h>
#include <issuance.h>
#include <rpc/util.h>
#include <future>
#include <stdint.h>
@ -342,7 +347,7 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request)
return res;
}
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf)
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in)
{
if (inputs_in.isNull() || outputs_in.isNull())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null");
@ -362,6 +367,11 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
bool rbfOptIn = rbf.isTrue();
UniValue assets;
if (!assets_in.isNull()) {
assets = assets_in.get_obj();
}
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
const UniValue& input = inputs[idx];
const UniValue& o = input.get_obj();
@ -416,11 +426,22 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
}
outputs = std::move(outputs_dict);
}
// Keep track of the fee output so we can add it in the very end of the transaction.
CTxOut fee_out;
for (const std::string& name_ : outputs.getKeys()) {
// ELEMENTS:
// Asset defaults to policyAsset
CAsset asset(::policyAsset);
if (!assets.isNull()) {
if (!find_value(assets, name_).isNull()) {
asset = CAsset(ParseHashO(assets, name_));
}
}
if (name_ == "data") {
std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data");
CTxOut out(0, CScript() << OP_RETURN << data);
CTxOut out(asset, 0, CScript() << OP_RETURN << data);
rawTx.vout.push_back(out);
} else if (name_ == "vdata") {
// ELEMENTS: support multi-push OP_RETURN
@ -431,9 +452,12 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
datascript << data;
}
//TODO(rebase) CA asset
CTxOut out(0, datascript);
CTxOut out(asset, 0, datascript);
rawTx.vout.push_back(out);
} else if (name_ == "fee") {
// ELEMENTS: explicit fee outputs
CAmount nAmount = AmountFromValue(outputs[name_]);
fee_out = CTxOut(asset, nAmount, CScript());
} else {
CTxDestination destination = DecodeDestination(name_);
if (!IsValidDestination(destination)) {
@ -447,11 +471,20 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
CScript scriptPubKey = GetScriptForDestination(destination);
CAmount nAmount = AmountFromValue(outputs[name_]);
CTxOut out(nAmount, scriptPubKey);
CTxOut out(asset, nAmount, scriptPubKey);
if (IsBlindDestination(destination)) {
CPubKey blind_pub = GetDestinationBlindingKey(destination);
out.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
}
rawTx.vout.push_back(out);
}
}
// Add fee output in the end.
if (!fee_out.nValue.IsNull() && fee_out.nValue.GetAmount() > 0) {
rawTx.vout.push_back(fee_out);
}
if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(rawTx)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
}
@ -461,10 +494,10 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
static UniValue createrawtransaction(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) {
throw std::runtime_error(
// clang-format off
"createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n"
"createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( {\"address\":\"asset\"} )\n"
"\nCreate a transaction spending the given inputs and creating new outputs.\n"
"Outputs can be addresses or data.\n"
"Returns hex-encoded raw transaction.\n"
@ -489,6 +522,9 @@ static UniValue createrawtransaction(const JSONRPCRequest& request)
" {\n"
" \"data\": \"hex\" , (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n"
" \"vdata\": [\"hex\"] (string, optional) The key is \"vdata\", the value is an array of hex encoded data\n"
" },\n"
" {\n"
" \"fee\": x.xxx (numeric or string, optional) The key is \"fee\", the value the fee output you want to add.\n"
" }\n"
" ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
" accepted as second parameter.\n"
@ -496,6 +532,12 @@ static UniValue createrawtransaction(const JSONRPCRequest& request)
"3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
"4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n"
" Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"
"5. \"output_assets\" (strings, optional, default=bitcoin) A json object of assets to addresses\n"
" {\n"
" \"address\": \"hex\" \n"
" \"fee\": \"hex\" \n"
" ...\n"
" }\n"
"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
@ -512,11 +554,12 @@ static UniValue createrawtransaction(const JSONRPCRequest& request)
UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM,
UniValue::VBOOL
UniValue::VBOOL,
UniValue::VOBJ
}, true
);
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]);
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4]);
return EncodeHexTx(rawTx);
}
@ -639,7 +682,7 @@ static UniValue decodescript(const JSONRPCRequest& request)
if (type.isStr() && type.get_str() != "scripthash") {
// P2SH cannot be wrapped in a P2SH. If this script is already a P2SH,
// don't return the address for a P2SH of the P2SH.
r.pushKV("p2sh", EncodeDestination(CScriptID(script)));
r.pushKV("p2sh", EncodeDestination(ScriptHash(CScriptID(script))));
// P2SH and witness programs cannot be wrapped in P2WSH, if this script
// is a witness program, don't return addresses for a segwit programs.
if (type.get_str() == "pubkey" || type.get_str() == "pubkeyhash" || type.get_str() == "multisig" || type.get_str() == "nonstandard") {
@ -666,7 +709,7 @@ static UniValue decodescript(const JSONRPCRequest& request)
segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script));
}
ScriptPubKeyToUniv(segwitScr, sr, true);
sr.pushKV("p2sh-segwit", EncodeDestination(CScriptID(segwitScr)));
sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(CScriptID(segwitScr))));
r.pushKV("segwit", sr);
}
}
@ -833,9 +876,13 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival
}
Coin newcoin;
newcoin.out.scriptPubKey = scriptPubKey;
newcoin.out.nValue = MAX_MONEY;
newcoin.out.nValue = CConfidentialValue(MAX_MONEY);
if (prevOut.exists("amount")) {
newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount"));
newcoin.out.nValue = CConfidentialValue(AmountFromValue(find_value(prevOut, "amount")));
} else if (prevOut.exists("amountcommitment")) {
// Segwit sigs require the amount commitment to be sighashed
uint256 asset_commit = uint256S(prevOut["amountcommitment"].get_str());
newcoin.out.nValue.vchCommitment = std::vector<unsigned char>(asset_commit.begin(), asset_commit.end());
}
newcoin.nHeight = 1;
view.AddCoin(out, std::move(newcoin), true);
@ -895,9 +942,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival
}
const CScript& prevPubKey = txin.m_is_pegin ? GetPeginOutputFromWitness(txConst.witness.vtxinwit[i].m_pegin_witness).scriptPubKey : coin.out.scriptPubKey;
//TODO(rebase) CT
//const CConfidentialValue& amount = txin.m_is_pegin ? GetPeginOutputFromWitness(txConst.witness.vtxinwit[i].m_pegin_witness).nValue : coin.out.nValue;
const CAmount& amount = txin.m_is_pegin ? GetPeginOutputFromWitness(txConst.witness.vtxinwit[i].m_pegin_witness).nValue : coin.out.nValue;
const CConfidentialValue& amount = txin.m_is_pegin ? GetPeginOutputFromWitness(txConst.witness.vtxinwit[i].m_pegin_witness).nValue : coin.out.nValue;
SignatureData sigdata = DataFromTransaction(mtx, i, coin.out);
// Only sign SIGHASH_SINGLE if there's a corresponding output:
@ -908,7 +953,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival
UpdateTransaction(mtx, i, sigdata);
// amount must be specified for valid segwit signature
if (amount == MAX_MONEY && !inWitness.scriptWitness.IsNull()) {
if (amount.IsExplicit() && amount.GetAmount() == MAX_MONEY && !mtx.witness.vtxinwit[i].scriptWitness.IsNull()) {
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coin.out.ToString()));
}
@ -962,7 +1007,8 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
" \"vout\":n, (numeric, required) The output number\n"
" \"scriptPubKey\": \"hex\", (string, required) script key\n"
" \"redeemScript\": \"hex\", (string, required for P2SH or P2WSH) redeem script\n"
" \"amount\": value (numeric, required) The amount spent\n"
" \"amount\": value (numeric, required if non-confidential segwit output) The amount spent\n"
" \"amountcommitment\": \"hex\", (string, required if confidential segiwt output) The amount commitment spent\n"
" }\n"
" ,...\n"
" ]\n"
@ -1335,6 +1381,7 @@ UniValue decodepsbt(const JSONRPCRequest& request)
UniValue tx_univ(UniValue::VOBJ);
TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false);
result.pushKV("tx", tx_univ);
result.pushKV("fees", AmountMapToUniv(GetFeeMap(CTransaction(*psbtx.tx)), ""));
// Unknown data
UniValue unknowns(UniValue::VOBJ);
@ -1344,8 +1391,6 @@ UniValue decodepsbt(const JSONRPCRequest& request)
result.pushKV("unknown", unknowns);
// inputs
CAmount total_in = 0;
bool have_all_utxos = true;
UniValue inputs(UniValue::VARR);
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
const PSBTInput& input = psbtx.inputs[i];
@ -1356,8 +1401,11 @@ UniValue decodepsbt(const JSONRPCRequest& request)
UniValue out(UniValue::VOBJ);
out.pushKV("amount", ValueFromAmount(txout.nValue));
total_in += txout.nValue;
if (txout.nValue.IsExplicit()) {
out.pushKV("amount", ValueFromAmount(txout.nValue.GetAmount()));
} else {
out.pushKV("amountcommitment", txout.nValue.GetHex());
}
UniValue o(UniValue::VOBJ);
ScriptToUniv(txout.scriptPubKey, o, true);
@ -1367,9 +1415,6 @@ UniValue decodepsbt(const JSONRPCRequest& request)
UniValue non_wit(UniValue::VOBJ);
TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false);
in.pushKV("non_witness_utxo", non_wit);
total_in += input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n].nValue;
} else {
have_all_utxos = false;
}
// Partial sigs
@ -1441,7 +1486,6 @@ UniValue decodepsbt(const JSONRPCRequest& request)
result.pushKV("inputs", inputs);
// outputs
CAmount output_value = 0;
UniValue outputs(UniValue::VARR);
for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
const PSBTOutput& output = psbtx.outputs[i];
@ -1481,14 +1525,8 @@ UniValue decodepsbt(const JSONRPCRequest& request)
}
outputs.push_back(out);
// Fee calculation
output_value += psbtx.tx->vout[i].nValue;
}
result.pushKV("outputs", outputs);
if (have_all_utxos) {
result.pushKV("fee", ValueFromAmount(total_in - output_value));
}
return result;
}
@ -1641,6 +1679,12 @@ UniValue createpsbt(const JSONRPCRequest& request)
"3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
"4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n"
" Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"
"5. \"output_assets\" (strings, optional, default=bitcoin) A json object of assets to addresses\n"
" {\n"
" \"address\": \"hex\" \n"
" \"fee\": \"hex\" \n"
" ...\n"
" }\n"
"\nResult:\n"
" \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n"
"\nExamples:\n"
@ -1656,7 +1700,7 @@ UniValue createpsbt(const JSONRPCRequest& request)
}, true
);
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]);
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4]);
// Make a blank psbt
PartiallySignedTransaction psbtx;
@ -1744,12 +1788,483 @@ UniValue converttopsbt(const JSONRPCRequest& request)
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
}
UniValue rawblindrawtransaction(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() < 5 || request.params.size() > 7))
throw std::runtime_error(
"rawblindrawtransaction \"hexstring\" [\"inputblinder\",...] [\"inputamount\",...] [\"inputasset\",...] [\"inputassetblinder\",...] ( totalblinder, ignoreblindfail )\n"
"\nConvert one or more outputs of a raw transaction into confidential ones.\n"
"Returns the hex-encoded raw transaction.\n"
"The input raw transaction cannot have already-blinded outputs.\n"
"The output keys used can be specified by using a confidential address in createrawtransaction.\n"
"If an additional blinded output is required to make a balanced blinding, a 0-value unspendable output will be added. Since there is no access to the wallet the blinding pubkey from the last output with blinding key will be repeated.\n"
"You can not blind issuances with this call.\n"
"\nArguments:\n"
"1. \"hexstring\", (string, required) A hex-encoded raw transaction.\n"
"2. [ (array, required) An array with one entry per transaction input.\n"
" \"inputamountblinder\" (string, required) A hex-encoded blinding factor, one for each input.\n"
" Blinding factors can be found in the \"blinder\" output of listunspent.\n"
" ],\n"
"3. [ (array, required) An array with one entry per transaction input.\n"
" \"inputamount\" (numeric, required) An amount for each input.\n"
" ],\n"
"4. [ (array, required) An array with one entry per transaction input.\n"
" \"inputasset\" (string, required) A hex-encoded asset id, one for each input.\n"
" ],\n"
"5. [ (array, required) An array with one entry per transaction input.\n"
" \"inputassetblinder\" (string, required) A hex-encoded asset blinding factor, one for each input.\n"
" ],\n"
"6. \"totalblinder\" (string, optional) Ignored for now.\n"
"7. \"ignoreblindfail\"\" (bool, optional, default=true) Return a transaction even when a blinding attempt fails due to number of blinded inputs/outputs.\n"
"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
);
std::vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1"));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
CMutableTransaction tx;
try {
ssData >> tx;
} catch (const std::exception &) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
UniValue inputBlinds = request.params[1].get_array();
UniValue inputAmounts = request.params[2].get_array();
UniValue inputAssets = request.params[3].get_array();
UniValue inputAssetBlinds = request.params[4].get_array();
bool fIgnoreBlindFail = true;
if (!request.params[6].isNull()) {
fIgnoreBlindFail = request.params[6].get_bool();
}
int n_blinded_ins = 0;
if (inputBlinds.size() != tx.vin.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter: one (potentially empty) input blind for each input must be provided");
}
if (inputAmounts.size() != tx.vin.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter: one (potentially empty) input blind for each input must be provided");
}
if (inputAssets.size() != tx.vin.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter: one (potentially empty) input asset id for each input must be provided");
}
if (inputAssetBlinds.size() != tx.vin.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter: one (potentially empty) input asset blind for each input must be provided");
}
std::vector<CAmount> input_amounts;
std::vector<uint256> input_blinds;
std::vector<uint256> input_asset_blinds;
std::vector<CAsset> input_assets;
std::vector<uint256> output_value_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CAsset> output_assets;
std::vector<CPubKey> output_pubkeys;
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
if (!inputBlinds[nIn].isStr())
throw JSONRPCError(RPC_INVALID_PARAMETER, "input blinds must be an array of hex strings");
if (!inputAssetBlinds[nIn].isStr())
throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset blinds must be an array of hex strings");
if (!inputAssets[nIn].isStr())
throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset ids must be an array of hex strings");
std::string blind(inputBlinds[nIn].get_str());
std::string assetblind(inputAssetBlinds[nIn].get_str());
std::string asset(inputAssets[nIn].get_str());
if (!IsHex(blind) || blind.length() != 32*2)
throw JSONRPCError(RPC_INVALID_PARAMETER, "input blinds must be an array of 32-byte hex-encoded strings");
if (!IsHex(assetblind) || assetblind.length() != 32*2)
throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset blinds must be an array of 32-byte hex-encoded strings");
if (!IsHex(asset) || asset.length() != 32*2)
throw JSONRPCError(RPC_INVALID_PARAMETER, "input asset blinds must be an array of 32-byte hex-encoded strings");
input_blinds.push_back(uint256S(blind));
input_asset_blinds.push_back(uint256S(assetblind));
input_assets.push_back(CAsset(uint256S(asset)));
input_amounts.push_back(AmountFromValue(inputAmounts[nIn]));
if (!input_blinds.back().IsNull()) {
n_blinded_ins++;
}
}
RawFillBlinds(tx, output_value_blinds, output_asset_blinds, output_pubkeys);
// How many are we trying to blind?
int num_pubkeys = 0;
unsigned int keyIndex = -1;
for (unsigned int i = 0; i < output_pubkeys.size(); i++) {
const CPubKey& key = output_pubkeys[i];
if (key.IsValid()) {
num_pubkeys++;
keyIndex = i;
}
}
if (num_pubkeys == 0 && n_blinded_ins == 0) {
// Vacuous, just return the transaction
return EncodeHexTx(tx);
} else if (n_blinded_ins > 0 && num_pubkeys == 0) {
// No notion of wallet, cannot complete this blinding without passed-in pubkey
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Add another output to blind in order to complete the blinding.");
} else if (n_blinded_ins == 0 && num_pubkeys == 1) {
if (fIgnoreBlindFail) {
// Just get rid of the ECDH key in the nonce field and return
tx.vout[keyIndex].nNonce.SetNull();
return EncodeHexTx(tx);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Add another output to blind in order to complete the blinding.");
}
}
int ret = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, std::vector<CKey>(), std::vector<CKey>(), tx);
if (ret != num_pubkeys) {
// TODO Have more rich return values, communicating to user what has been blinded
// User may be ok not blinding something that for instance has no corresponding type on input
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?");
}
return EncodeHexTx(tx);
}
struct RawIssuanceDetails
{
int input_index;
uint256 entropy;
CAsset asset;
CAsset token;
};
// Appends a single issuance to the first input that doesn't have one, and includes
// a single output per asset type in shuffled positions.
void issueasset_base(CMutableTransaction& mtx, RawIssuanceDetails& issuance_details, const CAmount asset_amount, const CAmount token_amount, const std::string& asset_address_str, const std::string& token_address_str, const bool blind_issuance, const uint256& contract_hash)
{
CTxDestination asset_address(DecodeDestination(asset_address_str));
CTxDestination token_address(DecodeDestination(token_address_str));
CScript asset_destination = GetScriptForDestination(asset_address);
CScript token_destination = GetScriptForDestination(token_address);
// Find an input with no issuance field
size_t issuance_input_index = 0;
for (; issuance_input_index < mtx.vin.size(); issuance_input_index++) {
if (mtx.vin[issuance_input_index].assetIssuance.IsNull()) {
break;
}
}
// Can't add another one, exit
if (issuance_input_index == mtx.vin.size()) {
issuance_details.input_index = -1;
return;
}
uint256 entropy;
CAsset asset;
CAsset token;
GenerateAssetEntropy(entropy, mtx.vin[issuance_input_index].prevout, contract_hash);
CalculateAsset(asset, entropy);
CalculateReissuanceToken(token, entropy, blind_issuance);
issuance_details.input_index = issuance_input_index;
issuance_details.entropy = entropy;
issuance_details.asset = asset;
issuance_details.token = token;
mtx.vin[issuance_input_index].assetIssuance.assetEntropy = contract_hash;
// Place assets into randomly placed output slots, just insert in place
// -1 due to fee output being at the end no matter what.
int asset_place = GetRandInt(mtx.vout.size()-1);
int token_place = GetRandInt(mtx.vout.size()); // Don't bias insertion
CTxOut asset_out(asset, asset_amount, asset_destination);
// If blinded address, insert the pubkey into the nonce field for later substitution by blinding
if (IsBlindDestination(asset_address)) {
CPubKey asset_blind = GetDestinationBlindingKey(asset_address);
asset_out.nNonce.vchCommitment = std::vector<unsigned char>(asset_blind.begin(), asset_blind.end());
}
// Explicit 0 is represented by a null value, don't set to non-null in that case
if (blind_issuance || asset_amount != 0) {
mtx.vin[issuance_input_index].assetIssuance.nAmount = asset_amount;
}
// Don't make zero value output(impossible by consensus)
if (asset_amount > 0) {
mtx.vout.insert(mtx.vout.begin()+asset_place, asset_out);
}
CTxOut token_out(token, token_amount, token_destination);
// If blinded address, insert the pubkey into the nonce field for later substitution by blinding
if (IsBlindDestination(token_address)) {
CPubKey token_blind = GetDestinationBlindingKey(token_address);
token_out.nNonce.vchCommitment = std::vector<unsigned char>(token_blind.begin(), token_blind.end());
}
// Explicit 0 is represented by a null value, don't set to non-null in that case
if (blind_issuance || token_amount != 0) {
mtx.vin[issuance_input_index].assetIssuance.nInflationKeys = token_amount;
}
// Don't make zero value output(impossible by consensus)
if (token_amount > 0) {
mtx.vout.insert(mtx.vout.begin()+token_place, token_out);
}
}
// Appends a single reissuance to the specified input if none exists,
// and the corresponding output in a shuffled position. Errors otherwise.
void reissueasset_base(CMutableTransaction& mtx, int& issuance_input_index, const CAmount asset_amount, const std::string& asset_address_str, const uint256& asset_blinder, const uint256& entropy)
{
CTxDestination asset_address(DecodeDestination(asset_address_str));
CScript asset_destination = GetScriptForDestination(asset_address);
// Check if issuance already exists, error if already exists
if ((size_t)issuance_input_index >= mtx.vin.size() || !mtx.vin[issuance_input_index].assetIssuance.IsNull()) {
issuance_input_index = -1;
return;
}
CAsset asset;
CalculateAsset(asset, entropy);
mtx.vin[issuance_input_index].assetIssuance.assetEntropy = entropy;
mtx.vin[issuance_input_index].assetIssuance.assetBlindingNonce = asset_blinder;
mtx.vin[issuance_input_index].assetIssuance.nAmount = asset_amount;
// Place assets into randomly placed output slots, before change output, inserted in place
assert(mtx.vout.size() >= 1);
int asset_place = GetRandInt(mtx.vout.size()-1);
CTxOut asset_out(asset, asset_amount, asset_destination);
// If blinded address, insert the pubkey into the nonce field for later substitution by blinding
if (IsBlindDestination(asset_address)) {
CPubKey asset_blind = GetDestinationBlindingKey(asset_address);
asset_out.nNonce.vchCommitment = std::vector<unsigned char>(asset_blind.begin(), asset_blind.end());
}
assert(asset_amount > 0);
mtx.vout.insert(mtx.vout.begin()+asset_place, asset_out);
mtx.vin[issuance_input_index].assetIssuance.nAmount = asset_amount;
}
UniValue rawissueasset(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 2)
throw std::runtime_error(
"rawissueasset transaction [{\"asset_amount\":x.xxx, \"asset_address\":\"address\", \"token_amount\":x.xxx, \"token_address\":\"address\", \"blind\":bool, ( \"contract_hash\":\"hash\" )}, ...]\n"
"\nCreate an asset by attaching issuances to transaction inputs. Returns the transaction hex. There must be as many inputs as issuances requested. The final transaction hex is the final version of the transaction appended to the last object in the array.\n"
"\nArguments:\n"
"1. \"transaction\" (string, required) Transaction in hex in which to include an issuance input.\n"
"2. \"issuances\" (list, required) List of issuances to create. Each issuance must have one non-zero amount. \n"
"[\n"
" {\n"
" \"asset_amount\":x.xxx (numeric or string, optional) Amount of asset to generate, if any.\n"
" \"asset_address\":addr (string, optional) Destination address of generated asset. Required if `asset_amount` given.\n"
" \"token_amount\":x.xxx (numeric or string, optional) Amount of reissuance token to generate, if any.\n"
" \"token_address\":addr (string, optional) Destination address of generated reissuance tokens. Required if `token_amount` given.\n"
" \"blind\":bool (bool, optional, default=true) Whether to mark the issuance input for blinding or not. Only affects issuances with re-issuance tokens."
" \"contract_hash\":str (string, optional, default=00..00) Contract hash that is put into issuance definition. Must be 32 bytes worth in hex string form. This will affect the asset id."
" }\n"
" ...\n"
"]\n"
"\nResult:\n"
"[ (json array) Results of issuances, in the order of `issuances` argument\n"
" { (json object)\n"
" \"hex\":<hex>, (string) The transaction with issuances appended. Only appended to final index in returned array.\n"
" \"vin\":\"n\", (numeric) The input position of the issuance in the transaction.\n"
" \"entropy\":\"<entropy>\" (string) Entropy of the asset type.\n"
" \"asset\":\"<asset>\", (string) Asset type for issuance if known.\n"
" \"token\":\"<token>\", (string) Token type for issuance.\n"
" },\n"
" ...\n"
"]"
);
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
UniValue issuances = request.params[1].get_array();
std::string asset_address_str = "";
std::string token_address_str = "";
UniValue ret(UniValue::VARR);
// Count issuances, only append hex to final one
unsigned int issuances_til_now = 0;
for (unsigned int idx = 0; idx < issuances.size(); idx++) {
const UniValue& issuance = issuances[idx];
const UniValue& issuance_o = issuance.get_obj();
CAmount asset_amount = 0;
const UniValue& asset_amount_uni = issuance_o["asset_amount"];
if (asset_amount_uni.isNum()) {
asset_amount = AmountFromValue(asset_amount_uni);
if (asset_amount <= 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, asset_amount must be positive");
}
const UniValue& asset_address_uni = issuance_o["asset_address"];
if (!asset_address_uni.isStr()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing corresponding asset_address");
}
asset_address_str = asset_address_uni.get_str();
}
CAmount token_amount = 0;
const UniValue& token_amount_uni = issuance_o["token_amount"];
if (token_amount_uni.isNum()) {
token_amount = AmountFromValue(token_amount_uni);
if (token_amount <= 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, token_amount must be positive");
}
const UniValue& token_address_uni = issuance_o["token_address"];
if (!token_address_uni.isStr()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing corresponding token_address");
}
token_address_str = token_address_uni.get_str();
}
if (asset_amount == 0 && token_amount == 0) {
throw JSONRPCError(RPC_TYPE_ERROR, "Issuance must have one non-zero component");
}
// If we have issuances, check if reissuance tokens will be generated via blinding path
const UniValue blind_uni = issuance_o["blind"];
const bool blind_issuance = !blind_uni.isBool() || blind_uni.get_bool();
// Check for optional contract to hash into definition
uint256 contract_hash;
if (!issuance_o["contract_hash"].isNull()) {
contract_hash = ParseHashV(issuance_o["contract_hash"], "contract_hash");
}
RawIssuanceDetails details;
issueasset_base(mtx, details, asset_amount, token_amount, asset_address_str, token_address_str, blind_issuance, contract_hash);
if (details.input_index == -1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to find enough blank inputs for listed issuances.");
}
issuances_til_now++;
UniValue obj(UniValue::VOBJ);
if (issuances_til_now == issuances.size()) {
obj.pushKV("hex", EncodeHexTx(mtx, RPCSerializationFlags()));
}
obj.pushKV("vin", details.input_index);
obj.pushKV("entropy", details.entropy.GetHex());
obj.pushKV("asset", details.asset.GetHex());
obj.pushKV("token", details.token.GetHex());
ret.push_back(obj);
}
return ret;
}
UniValue rawreissueasset(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 2)
throw std::runtime_error(
"rawreissueasset transaction {\"vin\":\"n\", \"asset_amount\":x.xxx, \"asset_address\":\"address\", \"asset_blinder\":<hex>, \"entropy\":<hex>, ( \"contract_hash\":<hex> )}\n"
"\nRe-issue an asset by attaching pseudo-inputs to transaction inputs, revealing the underlying reissuance token of the input. Returns the transaction hex.\n"
"\nArguments:\n"
"1. \"transaction\" (string, required) Transaction in hex in which to include an issuance input.\n"
"2. \"reissuances\" (list, required) List of re-issuances to create. Each issuance must have one non-zero amount.\n"
"[\n"
" {\n"
" \"input_index\":\"n\", (numeric, required) The input position of the reissuance in the transaction.\n"
" \"asset_amount\":x.xxx, (numeric or string, required) Amount of asset to generate, if any.\n"
" \"asset_address\":addr, (string, required) Destination address of generated asset. Required if `asset_amount` given.\n"
" \"asset_blinder\":<hex>, (string, required) The blinding factor of the reissuance token output being spent.\n"
" \"entropy\":<hex>, (string, required) The `entropy` returned during initial issuance for the asset being reissued."
" }\n"
"\nResult:\n"
"{ (json object)\n"
" \"hex\":<hex>, (string) The transaction with reissuances appended.\n"
"}\n"
);
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
if (mtx.vout.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction must have at least one output.");
}
UniValue issuances = request.params[1].get_array();
unsigned int num_issuances = 0;
for (unsigned int idx = 0; idx < issuances.size(); idx++) {
const UniValue& issuance = issuances[idx];
const UniValue& issuance_o = issuance.get_obj();
CAmount asset_amount = 0;
const UniValue& asset_amount_uni = issuance_o["asset_amount"];
if (asset_amount_uni.isNum()) {
asset_amount = AmountFromValue(asset_amount_uni);
if (asset_amount <= 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, asset_amount must be positive");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Asset amount must be given for each reissuance.");
}
const UniValue& asset_address_uni = issuance_o["asset_address"];
if (!asset_address_uni.isStr()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Reissuance missing asset_address");
}
std::string asset_address_str = asset_address_uni.get_str();
int input_index = -1;
const UniValue& input_index_o = issuance_o["input_index"];
if (input_index_o.isNum()) {
input_index = input_index_o.get_int();
if (input_index < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Input index must be non-negative.");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Input indexes for all reissuances are required.");
}
uint256 asset_blinder = ParseHashV(issuance_o["asset_blinder"], "asset_blinder");
uint256 entropy = ParseHashV(issuance_o["entropy"], "entropy");
reissueasset_base(mtx, input_index, asset_amount, asset_address_str, asset_blinder, entropy);
if (input_index == -1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Selected transaction input already has issuance data.");
}
num_issuances++;
}
if (num_issuances != issuances.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to find enough blank inputs for listed issuances.");
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hex", EncodeHexTx(mtx, RPCSerializationFlags()));
return ret;
}
// clang-format off
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
{ "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} },
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable"} },
{ "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable", "output_assets"} },
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring","iswitness"} },
{ "rawtransactions", "decodescript", &decodescript, {"hexstring"} },
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","allowhighfees"} },
@ -1762,9 +2277,12 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} },
{ "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} },
{ "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} },
{ "rawtransactions", "rawblindrawtransaction", &rawblindrawtransaction, {"hexstring", "inputblinder", "inputamount", "inputasset", "inputassetblinder", "totalblinder", "ignoreblindfail"} },
{ "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} },
{ "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} },
{ "rawtransactions", "rawissueasset", &rawissueasset, {"transaction", "issuances"}},
{ "rawtransactions", "rawreissueasset", &rawreissueasset, {"transaction", "reissuances"}},
};
// clang-format on

View file

@ -13,6 +13,6 @@ class UniValue;
UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType);
/** Create a transaction from univalue parameters */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf);
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in);
#endif // BITCOIN_RPC_RAWTRANSACTION_H

View file

@ -8,6 +8,9 @@
#include <rpc/util.h>
#include <tinyformat.h>
#include <utilstrencodings.h>
#include <policy/policy.h>
#include <assetsdir.h>
#include <core_io.h>
// Converts a hex string to a public key if possible
CPubKey HexToPubKey(const std::string& hex_in)
@ -76,7 +79,7 @@ public:
return UniValue(UniValue::VOBJ);
}
UniValue operator()(const CKeyID& keyID) const
UniValue operator()(const PKHash& keyID) const
{
UniValue obj(UniValue::VOBJ);
obj.pushKV("isscript", false);
@ -84,7 +87,7 @@ public:
return obj;
}
UniValue operator()(const CScriptID& scriptID) const
UniValue operator()(const ScriptHash& scriptID) const
{
UniValue obj(UniValue::VOBJ);
obj.pushKV("isscript", true);
@ -134,3 +137,172 @@ UniValue DescribeAddress(const CTxDestination& dest)
{
return boost::apply_visitor(DescribeAddressVisitor(), dest);
}
// ELEMENTS
class BlindingPubkeyVisitor : public boost::static_visitor<CPubKey>
{
public:
explicit BlindingPubkeyVisitor() {}
CPubKey operator()(const CNoDestination& dest) const
{
return CPubKey();
}
CPubKey operator()(const PKHash& keyID) const
{
return keyID.blinding_pubkey;
}
CPubKey operator()(const ScriptHash& scriptID) const
{
return scriptID.blinding_pubkey;
}
CPubKey operator()(const WitnessV0KeyHash& id) const
{
return id.blinding_pubkey;
}
CPubKey operator()(const WitnessV0ScriptHash& id) const
{
return id.blinding_pubkey;
}
CPubKey operator()(const WitnessUnknown& id) const
{
return id.blinding_pubkey;
}
CPubKey operator()(const NullData& id) const
{
return CPubKey();
}
};
CPubKey GetDestinationBlindingKey(const CTxDestination& dest) {
return boost::apply_visitor(BlindingPubkeyVisitor(), dest);
}
bool IsBlindDestination(const CTxDestination& dest) {
return GetDestinationBlindingKey(dest).IsFullyValid();
}
class DescribeBlindAddressVisitor : public boost::static_visitor<UniValue>
{
public:
explicit DescribeBlindAddressVisitor() {}
UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const PKHash& pkhash) const
{
UniValue obj(UniValue::VOBJ);
const CPubKey& blind_pub = pkhash.blinding_pubkey;
if (IsBlindDestination(pkhash)) {
obj.pushKV("confidential_key", HexStr(blind_pub.begin(), blind_pub.end()));
PKHash unblinded(pkhash);
unblinded.blinding_pubkey = CPubKey();
obj.pushKV("unconfidential", EncodeDestination(unblinded));
} else {
obj.pushKV("confidential_key", "");
obj.pushKV("unconfidential", EncodeDestination(pkhash));
}
return obj;
}
UniValue operator()(const ScriptHash& scripthash) const
{
UniValue obj(UniValue::VOBJ);
const CPubKey& blind_pub = scripthash.blinding_pubkey;
if (IsBlindDestination(scripthash)) {
obj.pushKV("confidential_key", HexStr(blind_pub.begin(), blind_pub.end()));
ScriptHash unblinded(scripthash);
unblinded.blinding_pubkey = CPubKey();
obj.pushKV("unconfidential", EncodeDestination(unblinded));
} else {
obj.pushKV("confidential_key", "");
obj.pushKV("unconfidential", EncodeDestination(scripthash));
}
return obj;
}
UniValue operator()(const WitnessV0KeyHash& id) const
{
UniValue obj(UniValue::VOBJ);
const CPubKey& blind_pub = id.blinding_pubkey;
if (IsBlindDestination(id)) {
obj.pushKV("confidential_key", HexStr(blind_pub.begin(), blind_pub.end()));
WitnessV0KeyHash unblinded(id);
unblinded.blinding_pubkey = CPubKey();
obj.pushKV("unconfidential", EncodeDestination(unblinded));
} else {
obj.pushKV("confidential_key", "");
obj.pushKV("unconfidential", EncodeDestination(id));
}
return obj;
}
UniValue operator()(const WitnessV0ScriptHash& id) const
{
UniValue obj(UniValue::VOBJ);
const CPubKey& blind_pub = id.blinding_pubkey;
if (IsBlindDestination(id)) {
obj.pushKV("confidential_key", HexStr(blind_pub.begin(), blind_pub.end()));
WitnessV0ScriptHash unblinded(id);
unblinded.blinding_pubkey = CPubKey();
obj.pushKV("unconfidential", EncodeDestination(unblinded));
} else {
obj.pushKV("confidential_key", "");
obj.pushKV("unconfidential", EncodeDestination(id));
}
return obj;
}
UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const NullData& id) const { return NullUniValue; }
};
UniValue DescribeBlindAddress(const CTxDestination& dest)
{
UniValue ret(UniValue::VOBJ);
ret.pushKVs(boost::apply_visitor(DescribeBlindAddressVisitor(), dest));
return ret;
}
// Attaches labeled balance reports to UniValue obj with asset filter
// "" displays *all* assets as VOBJ pairs, while named assets must have
// been entered via -assetdir configuration argument and are returned as VNUM.
UniValue AmountMapToUniv(const CAmountMap& balanceOrig, std::string strasset)
{
// Make sure the policyAsset is always present in the balance map.
CAmountMap balance = balanceOrig;
balance[::policyAsset] += 0;
// If we don't do assets or a specific asset is given, we filter out once asset.
if (!g_con_elementsmode || strasset != "") {
if (g_con_elementsmode) {
return ValueFromAmount(balance[GetAssetFromString(strasset)]);
} else {
return ValueFromAmount(balance[::policyAsset]);
}
}
UniValue obj(UniValue::VOBJ);
for(std::map<CAsset, CAmount>::const_iterator it = balance.begin(); it != balance.end(); ++it) {
// Unknown assets
if (it->first.IsNull())
continue;
UniValue pair(UniValue::VOBJ);
std::string label = gAssetsDir.GetLabel(it->first);
if (label == "") {
label = it->first.GetHex();
}
obj.pushKV(label, ValueFromAmount(it->second));
}
return obj;
}

View file

@ -23,5 +23,10 @@ CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in);
CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys);
UniValue DescribeAddress(const CTxDestination& dest);
UniValue DescribeBlindAddress(const CTxDestination& dest);
CPubKey GetDestinationBlindingKey(const CTxDestination& dest);
bool IsBlindDestination(const CTxDestination& dest);
UniValue AmountMapToUniv(const CAmountMap& balanceOrig, std::string strasset);
#endif // BITCOIN_RPC_UTIL_H

View file

@ -76,7 +76,7 @@ static bool verify_flags(unsigned int flags)
return (flags & ~(bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL)) == 0;
}
static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CAmount amount,
static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CConfidentialValue amount,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
{
@ -102,12 +102,20 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
}
}
int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount,
int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
const unsigned char *amount, unsigned int amountLen,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
{
CAmount am(amount);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
try {
TxInputStream stream(SER_NETWORK, PROTOCOL_VERSION, amount, amountLen);
CConfidentialValue am;
stream >> am;
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
} catch (const std::exception&) {
return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
}
}
@ -119,7 +127,7 @@ int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned i
return set_error(err, bitcoinconsensus_ERR_AMOUNT_REQUIRED);
}
CAmount am(0);
CConfidentialValue am(0);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
}

View file

@ -68,7 +68,8 @@ EXPORT_SYMBOL int bitcoinconsensus_verify_script(const unsigned char *scriptPubK
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);
EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount,
EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
const unsigned char *amount, unsigned int amountLen,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);

View file

@ -228,7 +228,7 @@ public:
}
};
CScript P2PKHGetScript(const CPubKey& pubkey) { return GetScriptForDestination(pubkey.GetID()); }
CScript P2PKHGetScript(const CPubKey& pubkey) { return GetScriptForDestination(PKHash(pubkey)); }
CScript P2PKGetScript(const CPubKey& pubkey) { return GetScriptForRawPubKey(pubkey); }
CScript P2WPKHGetScript(const CPubKey& pubkey) { return GetScriptForDestination(WitnessV0KeyHash(pubkey.GetID())); }
@ -320,7 +320,7 @@ public:
}
};
CScript ConvertP2SH(const CScript& script) { return GetScriptForDestination(CScriptID(script)); }
CScript ConvertP2SH(const CScript& script) { return GetScriptForDestination(ScriptHash(script)); }
CScript ConvertP2WSH(const CScript& script) { return GetScriptForDestination(WitnessV0ScriptHash(script)); }
/** A parsed combo(P) descriptor. */
@ -347,14 +347,14 @@ public:
CKeyID keyid = key.GetID();
{
CScript p2pk = GetScriptForRawPubKey(key);
CScript p2pkh = GetScriptForDestination(keyid);
CScript p2pkh = GetScriptForDestination(PKHash(keyid));
output_scripts = std::vector<CScript>{std::move(p2pk), std::move(p2pkh)};
out.pubkeys.emplace(keyid, key);
}
if (key.IsCompressed()) {
CScript p2wpkh = GetScriptForDestination(WitnessV0KeyHash(keyid));
CScriptID p2wpkh_id(p2wpkh);
CScript p2sh_p2wpkh = GetScriptForDestination(p2wpkh_id);
CScript p2sh_p2wpkh = GetScriptForDestination(ScriptHash(p2wpkh_id));
out.scripts.emplace(p2wpkh_id, p2wpkh);
output_scripts.push_back(std::move(p2wpkh));
output_scripts.push_back(std::move(p2sh_p2wpkh));

View file

@ -1519,6 +1519,11 @@ public:
::Serialize(s, (int)0);
else
::Serialize(s, txTo.vin[nInput].nSequence);
// Serialize the asset issuance object
if (!txTo.vin[nInput].assetIssuance.IsNull()) {
assert(g_con_elementsmode);
::Serialize(s, txTo.vin[nInput].assetIssuance);
}
}
/** Serialize an output of txTo */
@ -1571,6 +1576,19 @@ uint256 GetSequenceHash(const T& txTo)
return ss.GetHash();
}
template <class T>
uint256 GetIssuanceHash(const T& txTo)
{
CHashWriter ss(SER_GETHASH, 0);
for (const auto& txin : txTo.vin) {
if (txin.assetIssuance.IsNull())
ss << (unsigned char)0;
else
ss << txin.assetIssuance;
}
return ss.GetHash();
}
template <class T>
uint256 GetOutputsHash(const T& txTo)
{
@ -1590,6 +1608,7 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo)
if (txTo.HasWitness()) {
hashPrevouts = GetPrevoutHash(txTo);
hashSequence = GetSequenceHash(txTo);
hashIssuance = GetIssuanceHash(txTo);
hashOutputs = GetOutputsHash(txTo);
ready = true;
}
@ -1600,13 +1619,14 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
template <class T>
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
{
assert(nIn < txTo.vin.size());
if (sigversion == SigVersion::WITNESS_V0) {
uint256 hashPrevouts;
uint256 hashSequence;
uint256 hashIssuance;
uint256 hashOutputs;
const bool cacheready = cache && cache->ready;
@ -1618,6 +1638,9 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
hashSequence = cacheready ? cache->hashSequence : GetSequenceHash(txTo);
}
if (!(nHashType & SIGHASH_ANYONECANPAY)) {
hashIssuance = cacheready ? cache->hashIssuance : GetIssuanceHash(txTo);
}
if ((nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) {
hashOutputs = cacheready ? cache->hashOutputs : GetOutputsHash(txTo);
@ -1633,13 +1656,24 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
// Input prevouts/nSequence (none/all, depending on flags)
ss << hashPrevouts;
ss << hashSequence;
if (g_con_elementsmode) {
ss << hashIssuance;
}
// The input being signed (replacing the scriptSig with scriptCode + amount)
// The prevout may already be contained in hashPrevout, and the nSequence
// may already be contain in hashSequence.
ss << txTo.vin[nIn].prevout;
ss << scriptCode;
ss << amount;
if (g_con_elementsmode) {
ss << amount;
} else {
ss << amount.GetAmount();
}
ss << txTo.vin[nIn].nSequence;
if (!txTo.vin[nIn].assetIssuance.IsNull()) {
assert(g_con_elementsmode);
ss << txTo.vin[nIn].assetIssuance;
}
// Outputs (none/one/all, depending on flags)
ss << hashOutputs;
// Locktime

View file

@ -125,7 +125,7 @@ bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned i
struct PrecomputedTransactionData
{
uint256 hashPrevouts, hashSequence, hashOutputs;
uint256 hashPrevouts, hashSequence, hashOutputs, hashIssuance;
bool ready = false;
template <class T>
@ -143,7 +143,7 @@ static constexpr size_t WITNESS_V0_SCRIPTHASH_SIZE = 32;
static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20;
template <class T>
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
class BaseSignatureChecker
{
@ -172,15 +172,15 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker
private:
const T* txTo;
unsigned int nIn;
const CAmount amount;
const CConfidentialValue amount;
const PrecomputedTransactionData* txdata;
protected:
virtual bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
public:
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckSequence(const CScriptNum& nSequence) const override;

View file

@ -69,6 +69,7 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
case TX_NONSTANDARD:
case TX_NULL_DATA:
case TX_WITNESS_UNKNOWN:
case TX_FEE:
break;
case TX_PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();
@ -91,7 +92,7 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
// This also applies to the P2WSH case.
break;
}
ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));
ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));
break;
}
case TX_PUBKEYHASH:

View file

@ -20,6 +20,9 @@
#include <uint256.h>
#include <vector>
// IsUnspendable() compatibility
extern bool g_con_elementsmode;
// Maximum number of bytes pushable to the stack
static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520;
@ -570,7 +573,8 @@ public:
*/
bool IsUnspendable() const
{
return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE) ||
(g_con_elementsmode && size() == 0 /* Elements rule for fee outputs */);
}
void clear()

View file

@ -68,7 +68,11 @@ typedef enum ScriptError_t
SCRIPT_ERR_OP_CODESEPARATOR,
SCRIPT_ERR_SIG_FINDANDDELETE,
SCRIPT_ERR_ERROR_COUNT
SCRIPT_ERR_ERROR_COUNT,
// ELEMENTS:
SCRIPT_ERR_RANGEPROOF,
SCRIPT_ERR_PEDERSEN_TALLY
} ScriptError;
#define SCRIPT_ERR_LAST SCRIPT_ERR_ERROR_COUNT

View file

@ -41,6 +41,14 @@ public:
CSHA256().Write(nonce.begin(), 32).Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(&vchSig[0], vchSig.size()).Finalize(entry.begin());
}
// ELEMENTS:
void ComputeEntry(uint256& entry, const std::vector<unsigned char>& proof, const std::vector<unsigned char>& commitment) {
CSHA256().Write(nonce.begin(), nonce.size()).Write(proof.data(), proof.size()).Write(commitment.data(), commitment.size()).Finalize(entry.begin());
}
void ComputeEntry(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& proof, const std::vector<unsigned char>& commitment) {
CSHA256().Write(nonce.begin(), nonce.size()).Write(hash.begin(), 32).Write(proof.data(), proof.size()).Write(commitment.data(), commitment.size()).Finalize(entry.begin());
}
bool
Get(const uint256& entry, const bool erase)
{
@ -66,6 +74,11 @@ public:
* signatureCache could be made local to VerifySignature.
*/
static CSignatureCache signatureCache;
// ELEMENTS:
static CSignatureCache rangeProofCache;
static CSignatureCache surjectionProofCache;
} // namespace
// To be called once in AppInitMain/BasicTestingSetup to initialize the
@ -92,3 +105,102 @@ bool CachingTransactionSignatureChecker::VerifySignature(const std::vector<unsig
signatureCache.Set(entry);
return true;
}
//
// ELEMENTS CACHES
// To be called once in AppInit2/TestingSetup to initialize the rangeproof cache
void InitRangeproofCache()
{
// nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero,
// setup_bytes creates the minimum possible cache (2 elements).
size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE)), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
size_t nElems = rangeProofCache.setup_bytes(nMaxCacheSize);
LogPrintf("Using %zu MiB out of %zu requested for rangeproof cache, able to store %zu elements\n",
(nElems*sizeof(uint256)) >>20, nMaxCacheSize>>20, nElems);
}
// To be called once in AppInit2/TestingSetup to initialize the surjectionrproof cache
void InitSurjectionproofCache()
{
// nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero,
// setup_bytes creates the minimum possible cache (2 elements).
size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE)), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
size_t nElems = surjectionProofCache.setup_bytes(nMaxCacheSize);
LogPrintf("Using %zu MiB out of %zu requested for surjectionproof cache, able to store %zu elements\n",
(nElems*sizeof(uint256)) >>20, nMaxCacheSize>>20, nElems);
}
bool CachingRangeProofChecker::VerifyRangeProof(const std::vector<unsigned char>& vchRangeProof, const std::vector<unsigned char>& vchValueCommitment, const std::vector<unsigned char>& vchAssetCommitment, const CScript& scriptPubKey, const secp256k1_context* secp256k1_ctx_verify_amounts) const
{
uint256 entry;
rangeProofCache.ComputeEntry(entry, vchRangeProof, vchValueCommitment);
if (rangeProofCache.Get(entry, !store)) {
return true;
}
if (vchRangeProof.size() == 0) {
return false;
}
uint64_t min_value, max_value;
secp256k1_pedersen_commitment commit;
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &vchValueCommitment[0]) != 1)
return false;
secp256k1_generator tag;
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &tag, &vchAssetCommitment[0]) != 1)
return false;
if (!secp256k1_rangeproof_verify(secp256k1_ctx_verify_amounts, &min_value, &max_value, &commit, vchRangeProof.data(), vchRangeProof.size(), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &tag)) {
return false;
}
// An rangeproof is not valid if the output is spendable but the minimum number
// is 0. This is to prevent people passing 0-value tokens around, or conjuring
// reissuance tokens from nothing then attempting to reissue an asset.
// ie reissuance doesn't require revealing value of reissuance output
// Issuances proofs are always "unspendable" as they commit to an empty script.
if (min_value == 0 && !scriptPubKey.IsUnspendable()) {
return false;
}
if (store) {
rangeProofCache.Set(entry);
}
return true;
}
bool CachingSurjectionProofChecker::VerifySurjectionProof(secp256k1_surjectionproof& proof, std::vector<secp256k1_generator>& vTags, secp256k1_generator& gen, const secp256k1_context* secp256k1_ctx_verify_amounts, const uint256& wtxid) const
{
// Serialize proof
std::vector<unsigned char> vchproof;
size_t proof_len = secp256k1_surjectionproof_serialized_size(secp256k1_ctx_verify_amounts, &proof);
vchproof.resize(proof_len);
assert(secp256k1_surjectionproof_serialize(secp256k1_ctx_verify_amounts, vchproof.data(), &proof_len, &proof) == 1);
// wtxid commits to all data including surj targets
// we need to specify the proof and output asset point to be unique
uint256 entry;
surjectionProofCache.ComputeEntry(entry, wtxid, vchproof, std::vector<unsigned char>(std::begin(gen.data), std::end(gen.data)));
if (surjectionProofCache.Get(entry, !store)) {
return true;
}
if (secp256k1_surjectionproof_verify(secp256k1_ctx_verify_amounts, &proof, vTags.data(), vTags.size(), &gen) != 1) {
return false;
}
if (store) {
surjectionProofCache.Set(entry);
}
return true;
}
// END ELEMENTS
//

View file

@ -8,6 +8,9 @@
#include <script/interpreter.h>
#include <secp256k1.h>
#include <secp256k1_rangeproof.h>
#include <secp256k1_surjectionproof.h>
#include <vector>
// DoS prevention: limit cache size to 32MB (over 1000000 entries on 64-bit
@ -46,11 +49,46 @@ private:
bool store;
public:
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
};
void InitSignatureCache();
//
// ELEMENTS:
class CachingRangeProofChecker
{
private:
bool store;
public:
CachingRangeProofChecker(bool storeIn){
store = storeIn;
};
bool VerifyRangeProof(const std::vector<unsigned char>& vchRangeProof, const std::vector<unsigned char>& vchValueCommitment, const std::vector<unsigned char>& vchAssetCommitment, const CScript& scriptPubKey, const secp256k1_context* ctx) const;
};
class CachingSurjectionProofChecker
{
private:
bool store;
public:
CachingSurjectionProofChecker(bool storeIn){
store = storeIn;
};
bool VerifySurjectionProof(secp256k1_surjectionproof& proof, std::vector<secp256k1_generator>& vTags, secp256k1_generator& gen, const secp256k1_context* ctx, const uint256& wtxid) const;
};
void InitRangeproofCache();
void InitSurjectionproofCache();
// END ELEMENTS
//
#endif // BITCOIN_SCRIPT_SIGCACHE_H

View file

@ -15,7 +15,7 @@
typedef std::vector<unsigned char> valtype;
MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}
MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}
bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
{
@ -419,7 +419,7 @@ void SignatureData::MergeSignatureData(SignatureData sigdata)
signatures.insert(std::make_move_iterator(sigdata.signatures.begin()), std::make_move_iterator(sigdata.signatures.end()));
}
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType)
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CConfidentialValue& amount, int nHashType)
{
assert(nIn < txTo.vin.size());
txTo.witness.vtxinwit.resize(txTo.vin.size());

View file

@ -82,11 +82,11 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator {
const CMutableTransaction* txTo;
unsigned int nIn;
int nHashType;
CAmount amount;
CConfidentialValue amount;
const MutableTransactionSignatureChecker checker;
public:
MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL);
MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, int nHashTypeIn = SIGHASH_ALL);
const BaseSignatureChecker& Checker() const override { return checker; }
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override;
};
@ -703,7 +703,7 @@ struct PartiallySignedTransaction
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey, SignatureData& sigdata, unsigned int additional_flags=0);
/** Produce a script signature for a transaction. */
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType);
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CConfidentialValue& amount, int nHashType);
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType);
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */

View file

@ -21,11 +21,26 @@ unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY;
CScriptID::CScriptID(const CScript& in) : uint160(Hash160(in.begin(), in.end())) {}
ScriptHash::ScriptHash(const CScript& in) : uint160(Hash160(in.begin(), in.end())) {}
ScriptHash::ScriptHash(const CScript& in, const CPubKey& blinding_pubkey_in) : uint160(Hash160(in.begin(), in.end())), blinding_pubkey(blinding_pubkey_in) {}
ScriptHash::ScriptHash(const uint160& hash, const CPubKey& blinding_pubkey_in) : uint160(hash), blinding_pubkey(blinding_pubkey_in) {}
PKHash::PKHash(const CPubKey& pubkey) : uint160(pubkey.GetID()) {}
PKHash::PKHash(const CPubKey& pubkey, const CPubKey& blinding_pubkey_in) : uint160(pubkey.GetID()), blinding_pubkey(blinding_pubkey_in) {}
PKHash::PKHash(const uint160& hash, const CPubKey& blinding_pubkey_in) : uint160(hash), blinding_pubkey(blinding_pubkey_in) {}
WitnessV0ScriptHash::WitnessV0ScriptHash(const CScript& in)
{
CSHA256().Write(in.data(), in.size()).Finalize(begin());
}
WitnessV0ScriptHash::WitnessV0ScriptHash(const CScript& in, const CPubKey& blinding_pubkey_in)
{
CSHA256().Write(in.data(), in.size()).Finalize(begin());
blinding_pubkey = blinding_pubkey_in;
}
const char* GetTxnOutputType(txnouttype t)
{
switch (t)
@ -40,6 +55,7 @@ const char* GetTxnOutputType(txnouttype t)
case TX_WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
case TX_WITNESS_UNKNOWN: return "witness_unknown";
case TX_TRUE: return "true";
case TX_FEE: return "fee";
}
return nullptr;
}
@ -98,6 +114,11 @@ txnouttype Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned
return TX_TRUE;
}
// Fee outputs are for elements-style transactions only
if (g_con_elementsmode && scriptPubKey == CScript()) {
return TX_FEE;
}
// Shortcut for pay-to-script-hash, which are more constrained than the other types:
// it is always OP_HASH160 20 [20 byte hash] OP_EQUAL
if (scriptPubKey.IsPayToScriptHash())
@ -174,17 +195,17 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
if (!pubKey.IsValid())
return false;
addressRet = pubKey.GetID();
addressRet = PKHash(pubKey);
return true;
}
else if (whichType == TX_PUBKEYHASH)
{
addressRet = CKeyID(uint160(vSolutions[0]));
addressRet = PKHash(uint160(vSolutions[0]));
return true;
}
else if (whichType == TX_SCRIPTHASH)
{
addressRet = CScriptID(uint160(vSolutions[0]));
addressRet = ScriptHash(uint160(vSolutions[0]));
return true;
} else if (whichType == TX_WITNESS_V0_KEYHASH) {
WitnessV0KeyHash hash;
@ -229,7 +250,7 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::
if (!pubKey.IsValid())
continue;
CTxDestination address = pubKey.GetID();
CTxDestination address = PKHash(pubKey);
addressRet.push_back(address);
}
@ -262,13 +283,13 @@ public:
return false;
}
bool operator()(const CKeyID &keyID) const {
bool operator()(const PKHash &keyID) const {
script->clear();
*script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
return true;
}
bool operator()(const CScriptID &scriptID) const {
bool operator()(const ScriptHash &scriptID) const {
script->clear();
*script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL;
return true;

View file

@ -13,6 +13,8 @@
#include <stdint.h>
#include <pubkey.h> // blinding_pubkey
static const bool DEFAULT_ACCEPT_DATACARRIER = true;
class CKeyID;
@ -66,6 +68,8 @@ enum txnouttype
TX_WITNESS_V0_KEYHASH,
TX_WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
TX_TRUE, // For testing purposes only
// ELEMENTS:
TX_FEE,
};
class CNoDestination {
@ -74,19 +78,45 @@ public:
friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; }
};
struct PKHash : public uint160
{
PKHash() : uint160() {}
explicit PKHash(const uint160& hash) : uint160(hash) {}
explicit PKHash(const CPubKey& pubkey);
explicit PKHash(const CPubKey& pubkey, const CPubKey& blinding_pubkey);
explicit PKHash(const uint160& hash, const CPubKey& blinding_pubkey);
using uint160::uint160;
CPubKey blinding_pubkey;
};
struct ScriptHash : public uint160
{
ScriptHash() : uint160() {}
explicit ScriptHash(const uint160& hash) : uint160(hash) {}
explicit ScriptHash(const CScript& script);
explicit ScriptHash(const CScript& script, const CPubKey& blinding_pubkey);
explicit ScriptHash(const uint160& hash, const CPubKey& blinding_pubkey);
using uint160::uint160;
CPubKey blinding_pubkey;
};
struct WitnessV0ScriptHash : public uint256
{
WitnessV0ScriptHash() : uint256() {}
explicit WitnessV0ScriptHash(const uint256& hash) : uint256(hash) {}
explicit WitnessV0ScriptHash(const CScript& script);
explicit WitnessV0ScriptHash(const CScript& script, const CPubKey& blinding_pubkey);
using uint256::uint256;
CPubKey blinding_pubkey;
};
struct WitnessV0KeyHash : public uint160
{
WitnessV0KeyHash() : uint160() {}
explicit WitnessV0KeyHash(const uint160& hash) : uint160(hash) {}
explicit WitnessV0KeyHash(const uint160& hash, const CPubKey& blinding_pubkey_in) : uint160(hash), blinding_pubkey(blinding_pubkey_in) {}
using uint160::uint160;
CPubKey blinding_pubkey;
};
//! CTxDestination subtype to encode any future Witness version
@ -95,6 +125,7 @@ struct WitnessUnknown
unsigned int version;
unsigned int length;
unsigned char program[40];
CPubKey blinding_pubkey;
friend bool operator==(const WitnessUnknown& w1, const WitnessUnknown& w2) {
if (w1.version != w2.version) return false;
@ -129,15 +160,15 @@ public:
/**
* A txout script template with a specific destination. It is either:
* * CNoDestination: no destination set
* * CKeyID: TX_PUBKEYHASH destination (P2PKH)
* * CScriptID: TX_SCRIPTHASH destination (P2SH)
* * PKHash: TX_PUBKEYHASH destination (P2PKH)
* * ScriptHash: TX_SCRIPTHASH destination (P2SH)
* * WitnessV0ScriptHash: TX_WITNESS_V0_SCRIPTHASH destination (P2WSH)
* * WitnessV0KeyHash: TX_WITNESS_V0_KEYHASH destination (P2WPKH)
* * WitnessUnknown: TX_WITNESS_UNKNOWN destination (P2W???)
* * NullData: TX_NULL_DATA destination (OP_RETURN)
* A CTxDestination is the internal data type encoded in a bitcoin address
*/
typedef boost::variant<CNoDestination, CKeyID, CScriptID, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown, NullData> CTxDestination;
typedef boost::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown, NullData> CTxDestination;
/** Check whether a CTxDestination is a CNoDestination. */
bool IsValidDestination(const CTxDestination& dest);

370
src/test/blind_tests.cpp Normal file
View file

@ -0,0 +1,370 @@
// Copyright (c) 2013-2019 The Elements Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <arith_uint256.h>
#include <blind.h>
#include <coins.h>
#include <uint256.h>
#include <validation.h>
#include <test/test_bitcoin.h>
#include <boost/test/unit_test.hpp>
#include <secp256k1.h>
// For elements serialization rules
struct ElementsSetup : public TestingSetup {
ElementsSetup() : TestingSetup("custom") {}
};
BOOST_FIXTURE_TEST_SUITE(blind_tests, ElementsSetup)
// TODO: Make deterministic blinding wrapper function, test caching more exactly
BOOST_AUTO_TEST_CASE(naive_blinding_test)
{
CKey key1;
CKey key2;
CKey keyDummy;
// Any asset id will do
CAsset bitcoinID(GetRandHash());
CAsset otherID(GetRandHash());
CAsset unblinded_id;
uint256 asset_blind;
CScript op_true(OP_TRUE);
std::vector<CKey> vDummy;
unsigned char k1[32] = {1,2,3};
unsigned char k2[32] = {22,33,44};
unsigned char kDummy[32] = {133,144,155};
key1.Set(&k1[0], &k1[32], true);
key2.Set(&k2[0], &k2[32], true);
keyDummy.Set(&kDummy[0], &kDummy[32], true);
CPubKey pubkey1 = key1.GetPubKey();
CPubKey pubkey2 = key2.GetPubKey();
CPubKey pubkeyDummy = keyDummy.GetPubKey();
uint256 blind3, blind4, blindDummy;
std::vector<CTxOut> inputs;
CTxOut btc_oo(bitcoinID, 11, CScript());
CTxOut btc_ooo(bitcoinID, 111, CScript());
CTxOut other_fzz(otherID, 500, CScript());
CTxOut blind_ozz; // Will be computed later
{
inputs.clear();
inputs.push_back(btc_oo);
inputs.push_back(btc_ooo);
// Build a transaction that spends 2 unblinded coins (11, 111), and produces a single blinded one (100) and fee (22).
CMutableTransaction tx3;
tx3.vin.resize(2);
tx3.vin[0].prevout.hash = ArithToUint256(1);
tx3.vin[0].prevout.n = 0;
tx3.vin[1].prevout.hash = ArithToUint256(2);
tx3.vin[1].prevout.n = 0;
tx3.vout.resize(0);
tx3.vout.push_back(CTxOut(bitcoinID, 100, CScript() << OP_TRUE));
// Fee outputs are blank scriptpubkeys, and unblinded value/asset
tx3.vout.push_back(CTxOut(bitcoinID, 22, CScript()));
BOOST_CHECK(VerifyAmounts(inputs, tx3, nullptr, false));
// Malleate the output and check for correct handling of bad commitments
// These will fail IsValid checks
std::vector<unsigned char> asset_copy(tx3.vout[0].nAsset.vchCommitment);
std::vector<unsigned char> value_copy(tx3.vout[0].nValue.vchCommitment);
tx3.vout[0].nAsset.vchCommitment[0] = 122;
BOOST_CHECK(!VerifyAmounts(inputs, tx3, nullptr, false));
tx3.vout[0].nAsset.vchCommitment = asset_copy;
tx3.vout[0].nValue.vchCommitment[0] = 122;
BOOST_CHECK(!VerifyAmounts(inputs, tx3, nullptr, false));
tx3.vout[0].nValue.vchCommitment = value_copy;
// Make sure null values are handled correctly
tx3.vout[0].nAsset.SetNull();
BOOST_CHECK(!VerifyAmounts(inputs, tx3, nullptr, false));
tx3.vout[0].nAsset.vchCommitment = asset_copy;
tx3.vout[0].nValue.SetNull();
BOOST_CHECK(!VerifyAmounts(inputs, tx3, nullptr, false));
tx3.vout[0].nValue.vchCommitment = value_copy;
// Bad nonce values will result in failure to deserialize
tx3.vout[0].nNonce.SetNull();
BOOST_CHECK(VerifyAmounts(inputs, tx3, nullptr, false));
tx3.vout[0].nNonce.vchCommitment = tx3.vout[0].nValue.vchCommitment;
BOOST_CHECK(!VerifyAmounts(inputs, tx3, nullptr, false));
// Try to blind with a single non-fee output, which fails as its blinding factor ends up being zero.
std::vector<uint256> input_blinds;
std::vector<uint256> input_asset_blinds;
std::vector<CAsset> input_assets;
std::vector<CAmount> input_amounts;
std::vector<uint256> output_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CPubKey> output_pubkeys;
input_blinds.push_back(uint256());
input_blinds.push_back(uint256());
input_asset_blinds.push_back(uint256());
input_asset_blinds.push_back(uint256());
input_assets.push_back(bitcoinID);
input_assets.push_back(bitcoinID);
input_amounts.push_back(11);
input_amounts.push_back(111);
output_pubkeys.push_back(pubkey1);
output_pubkeys.push_back(CPubKey());
BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx3) == 0);
// Add a dummy output. Must be unspendable since it's 0-valued.
tx3.vout.push_back(CTxOut(bitcoinID, 0, CScript() << OP_RETURN));
output_pubkeys.push_back(pubkeyDummy);
BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx3) == 2);
BOOST_CHECK(!tx3.vout[0].nValue.IsExplicit());
BOOST_CHECK(!tx3.vout[2].nValue.IsExplicit());
BOOST_CHECK(VerifyAmounts(inputs, tx3, nullptr, false));
CAmount unblinded_amount;
BOOST_CHECK(UnblindConfidentialPair(key2, tx3.vout[0].nValue, tx3.vout[0].nAsset, tx3.vout[0].nNonce, op_true, tx3.witness.vtxoutwit[0].vchRangeproof, unblinded_amount, blind3, unblinded_id, asset_blind) == 0);
// Saving unblinded_id and asset_blind for later since we need for input
BOOST_CHECK(UnblindConfidentialPair(key1, tx3.vout[0].nValue, tx3.vout[0].nAsset, tx3.vout[0].nNonce, op_true, tx3.witness.vtxoutwit[0].vchRangeproof, unblinded_amount, blind3, unblinded_id, asset_blind) == 1);
BOOST_CHECK(unblinded_amount == 100);
BOOST_CHECK(unblinded_id == bitcoinID);
CAsset temp_asset;
uint256 temp_asset_blinder;
BOOST_CHECK(UnblindConfidentialPair(keyDummy, tx3.vout[2].nValue, tx3.vout[2].nAsset, tx3.vout[2].nNonce, CScript() << OP_RETURN, tx3.witness.vtxoutwit[2].vchRangeproof, unblinded_amount, blindDummy, temp_asset, temp_asset_blinder) == 1);
BOOST_CHECK(unblinded_amount == 0);
// Storing for next section
BOOST_CHECK(tx3.vout[0].nValue.IsCommitment());
BOOST_CHECK(tx3.vout[0].nAsset.IsCommitment());
blind_ozz = tx3.vout[0];
tx3.vout[1].nValue = CConfidentialValue(tx3.vout[1].nValue.GetAmount() - 1);
BOOST_CHECK(!VerifyAmounts(inputs, tx3, nullptr, false));
}
{
inputs.clear();
inputs.push_back(btc_ooo);
inputs.push_back(blind_ozz);
// Build a transactions that spends an unblinded (111) and blinded (100) coin, and produces only unblinded coins (impossible)
CMutableTransaction tx4;
tx4.vin.resize(2);
tx4.vin[0].prevout.hash = ArithToUint256(2);
tx4.vin[0].prevout.n = 0;
tx4.vin[1].prevout.hash = ArithToUint256(3);
tx4.vin[1].prevout.n = 0;
tx4.vout.push_back(CTxOut(bitcoinID, 30, CScript() << OP_TRUE));
tx4.vout.push_back(CTxOut(bitcoinID, 40, CScript() << OP_TRUE));
tx4.vout.push_back(CTxOut(bitcoinID, 111+100-30-40, CScript()));
BOOST_CHECK(!VerifyAmounts(inputs, tx4, nullptr, false)); // Spends a blinded coin with no blinded outputs to compensate.
std::vector<uint256> input_blinds;
std::vector<uint256> input_asset_blinds;
std::vector<CAsset> input_assets;
std::vector<CAmount> input_amounts;
std::vector<uint256> output_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CPubKey> output_pubkeys;
input_blinds.push_back(uint256());
input_blinds.push_back(blind3);
input_asset_blinds.push_back(uint256());
input_asset_blinds.push_back(asset_blind);
input_amounts.push_back(111);
input_amounts.push_back(100);
input_assets.push_back(unblinded_id);
input_assets.push_back(unblinded_id);
output_pubkeys.push_back(CPubKey());
output_pubkeys.push_back(CPubKey());
output_pubkeys.push_back(CPubKey());
BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx4) == 0); // Blinds nothing
}
{
inputs.clear();
inputs.push_back(btc_ooo);
inputs.push_back(blind_ozz);
// Build a transactions that spends an unblinded (111) and blinded (100) coin, and produces a blinded (30), unblinded (40), and blinded (50) coin and fee (91)
CMutableTransaction tx4;
tx4.vin.resize(2);
tx4.vin[0].prevout.hash = ArithToUint256(2);
tx4.vin[0].prevout.n = 0;
tx4.vin[1].prevout.hash = ArithToUint256(3);
tx4.vin[1].prevout.n = 0;
tx4.vout.push_back(CTxOut(bitcoinID, 30, CScript() << OP_TRUE));
tx4.vout.push_back(CTxOut(bitcoinID, 40, CScript() << OP_TRUE));
tx4.vout.push_back(CTxOut(bitcoinID, 50, CScript() << OP_TRUE));
// Fee
tx4.vout.push_back(CTxOut(bitcoinID, 111+100-30-40-50, CScript()));
BOOST_CHECK(!VerifyAmounts(inputs, tx4, nullptr, false)); // Spends a blinded coin with no blinded outputs to compensate.
std::vector<uint256> input_blinds;
std::vector<uint256> input_asset_blinds;
std::vector<CAsset> input_assets;
std::vector<CAmount> input_amounts;
std::vector<uint256> output_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CPubKey> output_pubkeys;
input_blinds.push_back(uint256());
input_blinds.push_back(blind3);
input_asset_blinds.push_back(uint256());
input_asset_blinds.push_back(asset_blind);
input_amounts.push_back(111);
input_amounts.push_back(100);
input_assets.push_back(unblinded_id);
input_assets.push_back(unblinded_id);
output_pubkeys.push_back(pubkey2);
output_pubkeys.push_back(CPubKey());
output_pubkeys.push_back(pubkey2);
output_pubkeys.push_back(CPubKey());
BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx4) == 2);
BOOST_CHECK(!tx4.vout[0].nValue.IsExplicit());
BOOST_CHECK(tx4.vout[1].nValue.IsExplicit());
BOOST_CHECK(!tx4.vout[2].nValue.IsExplicit());
// This one broken
BOOST_CHECK(VerifyAmounts(inputs, tx4, nullptr, false));
CAmount unblinded_amount;
CAsset asset_out;
uint256 asset_blinder_out;
BOOST_CHECK(UnblindConfidentialPair(key1, tx4.vout[0].nValue, tx4.vout[0].nAsset, tx4.vout[0].nNonce, op_true, tx4.witness.vtxoutwit[0].vchRangeproof, unblinded_amount, blind4, asset_out, asset_blinder_out) == 0);
BOOST_CHECK(UnblindConfidentialPair(key2, tx4.vout[0].nValue, tx4.vout[0].nAsset, tx4.vout[0].nNonce, op_true, tx4.witness.vtxoutwit[0].vchRangeproof, unblinded_amount, blind4, asset_out, asset_blinder_out) == 1);
BOOST_CHECK(unblinded_amount == 30);
BOOST_CHECK(asset_out == unblinded_id);
BOOST_CHECK(UnblindConfidentialPair(key2, tx4.vout[2].nValue, tx4.vout[2].nAsset, tx4.vout[2].nNonce, op_true, tx4.witness.vtxoutwit[2].vchRangeproof, unblinded_amount, blind4, asset_out, asset_blinder_out) == 1);
BOOST_CHECK(asset_out == unblinded_id);
BOOST_CHECK(unblinded_amount == 50);
// Commit to the wrong script in the rangeproof
BOOST_CHECK(UnblindConfidentialPair(key2, tx4.vout[2].nValue, tx4.vout[2].nAsset, tx4.vout[2].nNonce, CScript() << OP_FALSE, tx4.witness.vtxoutwit[2].vchRangeproof, unblinded_amount, blind4, asset_out, asset_blinder_out) == 0);
// Make invalid public keys in nonce commitment, first of right size
tx4.vout[2].nNonce.vchCommitment = std::vector<unsigned char>(33, 0);
tx4.vout[2].nNonce.vchCommitment[0] = 0x03;
BOOST_CHECK(UnblindConfidentialPair(key2, tx4.vout[2].nValue, tx4.vout[2].nAsset, tx4.vout[2].nNonce, op_true, tx4.witness.vtxoutwit[2].vchRangeproof, unblinded_amount, blind4, asset_out, asset_blinder_out) == 0);
// Next, leading byte claiming to be 33 bytes in size
tx4.vout[2].nNonce.vchCommitment.resize(1);
BOOST_CHECK(UnblindConfidentialPair(key2, tx4.vout[2].nValue, tx4.vout[2].nAsset, tx4.vout[2].nNonce, op_true, tx4.witness.vtxoutwit[2].vchRangeproof, unblinded_amount, blind4, asset_out, asset_blinder_out) == 0);
// Last, blank nonce commitment
tx4.vout[2].nNonce.vchCommitment.clear();
BOOST_CHECK(UnblindConfidentialPair(key2, tx4.vout[2].nValue, tx4.vout[2].nAsset, tx4.vout[2].nNonce, op_true, tx4.witness.vtxoutwit[2].vchRangeproof, unblinded_amount, blind4, asset_out, asset_blinder_out) == 0);
tx4.vout[3].nValue = CConfidentialValue(tx4.vout[3].nValue.GetAmount() - 1);
BOOST_CHECK(!VerifyAmounts(inputs, tx4, nullptr, false));
// Check wallet borromean-based rangeproof results against expected args
size_t proof_size = DEFAULT_RANGEPROOF_SIZE;
BOOST_CHECK(tx4.witness.vtxoutwit[2].vchRangeproof.size() == proof_size);
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
int exp = 0;
int mantissa = 0;
uint64_t min_value = 0;
uint64_t max_value = 0;
BOOST_CHECK(secp256k1_rangeproof_info(ctx, &exp, &mantissa, &min_value, &max_value, tx4.witness.vtxoutwit[2].vchRangeproof.data(), proof_size) == 1);
BOOST_CHECK(exp == 0);
BOOST_CHECK(mantissa == 36); // 36 bit default
BOOST_CHECK(min_value == 1);
BOOST_CHECK(max_value == 68719476736);
}
{
inputs.clear();
inputs.push_back(blind_ozz);
inputs.push_back(other_fzz);
// Spends 100 blinded bitcoin, 500 of unblinded "other"
CMutableTransaction tx5;
tx5.vin.resize(0);
tx5.vout.resize(0);
tx5.vin.push_back(CTxIn(COutPoint(ArithToUint256(3), 0)));
tx5.vin.push_back(CTxIn(COutPoint(ArithToUint256(5), 0)));
tx5.vout.push_back(CTxOut(bitcoinID, 29, CScript() << OP_TRUE));
tx5.vout.push_back(CTxOut(bitcoinID, 70, CScript() << OP_TRUE));
tx5.vout.push_back(CTxOut(otherID, 250, CScript() << OP_TRUE));
tx5.vout.push_back(CTxOut(otherID, 249, CScript() << OP_TRUE));
// Fees
tx5.vout.push_back(CTxOut(bitcoinID, 1, CScript()));
tx5.vout.push_back(CTxOut(otherID, 1, CScript()));
// Blinds don't balance
BOOST_CHECK(!VerifyAmounts(inputs, tx5, nullptr, false));
// Blinding setup stuff
std::vector<uint256> input_blinds;
std::vector<uint256> input_asset_blinds;
std::vector<CAsset> input_assets;
std::vector<CAmount> input_amounts;
std::vector<uint256> output_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CPubKey> output_pubkeys;
input_blinds.push_back(blind3);
input_blinds.push_back(uint256()); //
input_asset_blinds.push_back(asset_blind);
input_asset_blinds.push_back(uint256());
input_amounts.push_back(100);
input_amounts.push_back(500);
input_assets.push_back(bitcoinID);
input_assets.push_back(otherID);
for (unsigned int i = 0; i < 6; i++) {
output_pubkeys.push_back(pubkey2);
}
CMutableTransaction txtemp(tx5);
// No blinding keys for fees, bails out blinding nothing, still invalid due to imbalance
BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == -1);
BOOST_CHECK(!VerifyAmounts(inputs, txtemp, nullptr, false));
// Last will be implied blank keys
output_pubkeys.resize(4);
// Blind transaction, verify amounts
txtemp = tx5;
BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == 4);
BOOST_CHECK(VerifyAmounts(inputs, txtemp, nullptr, false));
// Transaction may not have spendable 0-value output
txtemp.vout.push_back(CTxOut(CAsset(), 0, CScript() << OP_TRUE));
BOOST_CHECK(!VerifyAmounts(inputs, txtemp, nullptr, false));
// Create imbalance by removing fees, should still be able to blind
txtemp = tx5;
txtemp.vout.resize(5);
BOOST_CHECK(!VerifyAmounts(inputs, txtemp, nullptr, false));
txtemp.vout.resize(4);
BOOST_CHECK(!VerifyAmounts(inputs, txtemp, nullptr, false));
BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == 4);
BOOST_CHECK(!VerifyAmounts(inputs, txtemp, nullptr, false));
txtemp = tx5;
// Remove other input, make surjection proof impossible for 2 "otherID" outputs
std::vector<uint256> t_input_blinds;
std::vector<uint256> t_input_asset_blinds;
std::vector<CAsset> t_input_assets;
std::vector<CAmount> t_input_amounts;
t_input_blinds = input_blinds;
t_input_asset_blinds = input_asset_blinds;
t_input_assets = input_assets;
t_input_amounts = input_amounts;
txtemp.vin.resize(1);
inputs.resize(1);
t_input_blinds.resize(1);
t_input_asset_blinds.resize(1);
t_input_assets.resize(1);
t_input_amounts.resize(1);
BOOST_CHECK(!VerifyAmounts(inputs, txtemp, nullptr, false));
BOOST_CHECK(BlindTransaction(t_input_blinds, t_input_asset_blinds, t_input_assets, t_input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == 2);
BOOST_CHECK(!VerifyAmounts(inputs, txtemp, nullptr, false));
}
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -5,6 +5,7 @@
#include <test/data/blockfilters.json.h>
#include <test/test_bitcoin.h>
#include <asset.h>
#include <blockfilter.h>
#include <core_io.h>
#include <serialize.h>
@ -61,13 +62,13 @@ BOOST_AUTO_TEST_CASE(blockfilter_basic_test)
excluded_scripts[1] << std::vector<unsigned char>(5, 33) << OP_CHECKSIG;
CMutableTransaction tx_1;
tx_1.vout.emplace_back(100, included_scripts[0]);
tx_1.vout.emplace_back(200, included_scripts[1]);
tx_1.vout.emplace_back(CTxOut(CAsset(), 100, included_scripts[0]));
tx_1.vout.emplace_back(CTxOut(CAsset(), 200, included_scripts[1]));
CMutableTransaction tx_2;
tx_2.vout.emplace_back(300, included_scripts[2]);
tx_2.vout.emplace_back(0, excluded_scripts[0]);
tx_2.vout.emplace_back(400, excluded_scripts[2]); // Script is empty
tx_2.vout.emplace_back(CTxOut(CAsset(), 300, included_scripts[2]));
tx_2.vout.emplace_back(CTxOut(CAsset(), 0, excluded_scripts[0]));
tx_2.vout.emplace_back(CTxOut(CAsset(), 400, excluded_scripts[2])); // Script is empty
CBlock block;
block.vtx.push_back(MakeTransactionRef(tx_1));
@ -75,9 +76,9 @@ BOOST_AUTO_TEST_CASE(blockfilter_basic_test)
CBlockUndo block_undo;
block_undo.vtxundo.emplace_back();
block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(500, included_scripts[3]), 1000, true);
block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(600, included_scripts[4]), 10000, false);
block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(700, excluded_scripts[2]), 100000, false);
block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(CAsset(), 500, included_scripts[3]), 1000, true);
block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(CAsset(), 600, included_scripts[4]), 10000, false);
block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(CAsset(), 700, excluded_scripts[2]), 100000, false);
BlockFilter block_filter(BlockFilterType::BASIC, block, block_undo);
const GCSFilter& filter = block_filter.GetFilter();
@ -125,7 +126,7 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test)
const UniValue& prev_scripts = test[pos++].get_array();
for (unsigned int ii = 0; ii < prev_scripts.size(); ii++) {
std::vector<unsigned char> raw_script = ParseHex(prev_scripts[ii].get_str());
CTxOut txout(0, CScript(raw_script.begin(), raw_script.end()));
CTxOut txout(CAsset(), 0, CScript(raw_script.begin(), raw_script.end()));
tx_undo.vprevout.emplace_back(txout, 0, false);
}

View file

@ -154,7 +154,7 @@ static void Correct_Queue_range(std::vector<size_t> range)
tg.create_thread([&]{small_queue->Thread();});
}
// Make vChecks here to save on malloc (this test can be slow...)
std::vector<FakeCheckCheckCompletion> vChecks;
std::vector<FakeCheckCheckCompletion*> vChecks;
for (const size_t i : range) {
size_t total = i;
FakeCheckCheckCompletion::n_calls = 0;
@ -226,10 +226,10 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Catches_Failure)
while (remaining) {
size_t r = InsecureRandRange(10);
std::vector<FailingCheck> vChecks;
vChecks.reserve(r);
for (size_t k = 0; k < r && remaining; k++, remaining--)
vChecks.emplace_back(remaining == 1);
std::vector<FailingCheck*> vChecks;
for (size_t k = 0; k < r && remaining; k++, remaining--) {
vChecks.push_back(new FailingCheck(remaining == 1));
}
control.Add(vChecks);
}
bool success = control.Wait();
@ -256,9 +256,11 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Recovers_From_Failure)
for (const bool end_fails : {true, false}) {
CCheckQueueControl<FailingCheck> control(fail_queue.get());
{
std::vector<FailingCheck> vChecks;
vChecks.resize(100, false);
vChecks[99] = end_fails;
std::vector<FailingCheck*> vChecks;
for (size_t i = 0; i < 100; i++) {
vChecks.push_back(new FailingCheck(false));
}
vChecks[99] = new FailingCheck(end_fails);
control.Add(vChecks);
}
bool r =control.Wait();
@ -287,9 +289,10 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck)
CCheckQueueControl<UniqueCheck> control(queue.get());
while (total) {
size_t r = InsecureRandRange(10);
std::vector<UniqueCheck> vChecks;
for (size_t k = 0; k < r && total; k++)
vChecks.emplace_back(--total);
std::vector<UniqueCheck*> vChecks;
for (size_t k = 0; k < r && total; k++) {
vChecks.emplace_back(new UniqueCheck(--total));
}
control.Add(vChecks);
}
}
@ -321,12 +324,12 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Memory)
CCheckQueueControl<MemoryCheck> control(queue.get());
while (total) {
size_t r = InsecureRandRange(10);
std::vector<MemoryCheck> vChecks;
std::vector<MemoryCheck*> vChecks;
for (size_t k = 0; k < r && total; k++) {
total--;
// Each iteration leaves data at the front, back, and middle
// to catch any sort of deallocation failure
vChecks.emplace_back(total == 0 || total == i || total == i/2);
vChecks.emplace_back(new MemoryCheck(total == 0 || total == i || total == i/2));
}
control.Add(vChecks);
}
@ -349,11 +352,12 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_FrozenCleanup)
}
std::thread t0([&]() {
CCheckQueueControl<FrozenCleanupCheck> control(queue.get());
std::vector<FrozenCleanupCheck> vChecks(1);
std::vector<FrozenCleanupCheck*> vChecks;
// Freezing can't be the default initialized behavior given how the queue
// swaps in default initialized Checks (otherwise freezing destructor
// would get called twice).
vChecks[0].should_freeze = true;
vChecks.push_back(new FrozenCleanupCheck());
vChecks[0]->should_freeze = true;
control.Add(vChecks);
control.Wait(); // Hangs here
});

View file

@ -481,8 +481,8 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
ss1 >> cc1;
BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
BOOST_CHECK_EQUAL(cc1.nHeight, 203998U);
BOOST_CHECK_EQUAL(cc1.out.nValue, CAmount{60000000000});
BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
BOOST_CHECK_EQUAL(cc1.out.nValue.GetAmount(), 60000000000);
BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
// Good example
CDataStream ss2(ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
@ -490,8 +490,8 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
ss2 >> cc2;
BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
BOOST_CHECK_EQUAL(cc2.nHeight, 120891U);
BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
BOOST_CHECK_EQUAL(cc2.out.nValue.GetAmount(), 110397);
BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
// Smallest possible example
CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
@ -499,7 +499,7 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
ss3 >> cc3;
BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
BOOST_CHECK_EQUAL(cc3.nHeight, 0U);
BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
BOOST_CHECK_EQUAL(cc3.out.nValue.GetAmount(), 0);
BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0U);
// scriptPubKey that ends beyond the end of the stream
@ -577,7 +577,7 @@ void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
if (it->second.coin.IsSpent()) {
value = PRUNED;
} else {
value = it->second.coin.out.nValue;
value = it->second.coin.out.nValue.GetAmount();
}
flags = it->second.flags;
assert(flags != NO_ENTRY);

View file

@ -349,7 +349,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
tx.vin[0].scriptSig << OP_1;
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID());
tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
AddOrphanTx(MakeTransactionRef(tx), i);
}
@ -365,7 +365,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
tx.vin[0].prevout.hash = txPrev->GetHash();
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID());
tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL);
AddOrphanTx(MakeTransactionRef(tx), i);
@ -379,7 +379,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
CMutableTransaction tx;
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID());
tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
tx.vin.resize(2777);
for (unsigned int j = 0; j < tx.vin.size(); j++)
{

View file

@ -68,10 +68,10 @@ BOOST_AUTO_TEST_CASE(key_test1)
BOOST_CHECK(!key2C.VerifyPubKey(pubkey2));
BOOST_CHECK(key2C.VerifyPubKey(pubkey2C));
BOOST_CHECK(DecodeDestination(addr1) == CTxDestination(pubkey1.GetID()));
BOOST_CHECK(DecodeDestination(addr2) == CTxDestination(pubkey2.GetID()));
BOOST_CHECK(DecodeDestination(addr1C) == CTxDestination(pubkey1C.GetID()));
BOOST_CHECK(DecodeDestination(addr2C) == CTxDestination(pubkey2C.GetID()));
BOOST_CHECK(DecodeDestination(addr1) == CTxDestination(PKHash(pubkey1)));
BOOST_CHECK(DecodeDestination(addr2) == CTxDestination(PKHash(pubkey2)));
BOOST_CHECK(DecodeDestination(addr1C) == CTxDestination(PKHash(pubkey1C)));
BOOST_CHECK(DecodeDestination(addr2C) == CTxDestination(PKHash(pubkey2C)));
for (int n=0; n<16; n++)
{

View file

@ -160,7 +160,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript&
// of the transactions is below the min relay fee
// Remove the low fee transaction and replace with a higher fee transaction
mempool.removeRecursive(tx);
tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee
tx.vout[0].nValue = tx.vout[0].nValue.GetAmount() - 2; // Now we should be just over the min relay fee
hashLowFeeTx = tx.GetHash();
mempool.addUnchecked(entry.Fee(feeToUse+2).FromTx(tx));
pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
@ -273,7 +273,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].nValue = BLOCKSUBSIDY;
for (unsigned int i = 0; i < 1001; ++i)
{
tx.vout[0].nValue -= LOWFEE;
tx.vout[0].nValue = tx.vout[0].nValue.GetAmount() - LOWFEE;
hash = tx.GetHash();
bool spendsCoinbase = i == 0; // only first tx spends coinbase
// If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails
@ -288,7 +288,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].nValue = BLOCKSUBSIDY;
for (unsigned int i = 0; i < 1001; ++i)
{
tx.vout[0].nValue -= LOWFEE;
tx.vout[0].nValue = tx.vout[0].nValue.GetAmount() - LOWFEE;
hash = tx.GetHash();
bool spendsCoinbase = i == 0; // only first tx spends coinbase
// If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes
@ -309,7 +309,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vout[0].nValue = BLOCKSUBSIDY;
for (unsigned int i = 0; i < 128; ++i)
{
tx.vout[0].nValue -= LOWFEE;
tx.vout[0].nValue = tx.vout[0].nValue.GetAmount() - LOWFEE;
hash = tx.GetHash();
bool spendsCoinbase = i == 0; // only first tx spends coinbase
mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
@ -335,7 +335,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vin[1].scriptSig = CScript() << OP_1;
tx.vin[1].prevout.hash = txFirst[0]->GetHash();
tx.vin[1].prevout.n = 0;
tx.vout[0].nValue = tx.vout[0].nValue+BLOCKSUBSIDY-HIGHERFEE; //First txn output + fresh coinbase - new txn fee
tx.vout[0].nValue = tx.vout[0].nValue.GetAmount() + BLOCKSUBSIDY - HIGHERFEE; //First txn output + fresh coinbase - new txn fee
hash = tx.GetHash();
mempool.addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
@ -399,12 +399,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
tx.vin[0].scriptSig = CScript() << OP_1;
tx.vout[0].nValue = BLOCKSUBSIDY-LOWFEE;
script = CScript() << OP_0;
tx.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(script));
tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script));
hash = tx.GetHash();
mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
tx.vin[0].prevout.hash = hash;
tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end());
tx.vout[0].nValue -= LOWFEE;
tx.vout[0].nValue = tx.vout[0].nValue.GetAmount() - LOWFEE;
hash = tx.GetHash();
mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
// Should throw block-validation-failed

Some files were not shown because too many files have changed in this diff Show more