diff --git a/examples/wakustealthcommitments/README.md b/examples/wakustealthcommitments/README.md new file mode 100644 index 0000000000..f577188c14 --- /dev/null +++ b/examples/wakustealthcommitments/README.md @@ -0,0 +1,38 @@ +# wakustealthcommitments + +This application/tool/protocol is used to securely communicate requests and responses for the [Stealth Address Scheme](https://eips.ethereum.org/EIPS/eip-5564) + +Uses TWN config as default, and content topic: `/wakustealthcommitments/1/app/proto` + +## Usage + +1. Clone the erc-5564-bn254 repo and build the static lib +```sh +gh repo clone rymnc/erc-5564-bn254 +cd erc-5564-bn254 +cargo build --release --all-features +cp ./target/release/liberc_5564_bn254.a +``` + +> ![NOTE] +> This static library also includes the rln ffi library, so you don't need to build it separately. +> This is because using both of them separately brings in a lot of duplicate symbols. + +2. Build the wakustealthcommitments app +```sh +cd +source env.sh +nim c --out:build/wakustealthcommitments --verbosity:0 --hints:off -d:chronicles_log_level=INFO -d:git_version="v0.24.0-rc.0-62-g7da25c" -d:release --passL:-lm --passL:liberc_5564_bn254.a --debugger:native examples/wakustealthcommitments/wakustealthcommitments.nim +``` + +3. +```sh +./build/wakustealthcommitments \ + --rln-relay-eth-client-address: \ + --rln-relay-cred-path: \ + --rln-relay-cred-password: +``` + +This service listens for requests for stealth commitment/address generation, +partakes in the generation of said stealth commitment and then distributes the response to the mesh. + diff --git a/examples/wakustealthcommitments/erc_5564_interface.nim b/examples/wakustealthcommitments/erc_5564_interface.nim new file mode 100644 index 0000000000..94ed4accbb --- /dev/null +++ b/examples/wakustealthcommitments/erc_5564_interface.nim @@ -0,0 +1,135 @@ +## Nim wrappers for the functions defined in librln +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +import stew/results + +###################################################################### +## ERC-5564-BN254 module APIs +###################################################################### + +type CErrorCode* = uint8 + +type CG1Projective* = object + x0: array[32, uint8] + +type CReturn*[T] = object + value: T + err_code: CErrorCode + +type CFr* = object + x0: array[32, uint8] + +type CStealthCommitment* = object + stealth_commitment: CG1Projective + view_tag: uint64 + +type CKeyPair* = object + private_key: CFr + public_key: CG1Projective + +proc drop_ffi_derive_public_key*(ptrx: ptr CReturn[CG1Projective]) {.importc: "drop_ffi_derive_public_key".} + +proc drop_ffi_generate_random_fr*(ptrx: ptr CReturn[CFr]) {.importc: "drop_ffi_generate_random_fr".} + +proc drop_ffi_generate_stealth_commitment*(ptrx: ptr CReturn[CStealthCommitment]) {.importc: "drop_ffi_generate_stealth_commitment".} + +proc drop_ffi_generate_stealth_private_key*(ptrx: ptr CReturn[CFr]) {.importc: "drop_ffi_generate_stealth_private_key".} + +proc drop_ffi_random_keypair*(ptrx: ptr CReturn[CKeyPair]) {.importc: "drop_ffi_random_keypair".} + +proc ffi_derive_public_key*(private_key: ptr CFr): (ptr CReturn[CG1Projective]) {.importc: "ffi_derive_public_key".} + +proc ffi_generate_random_fr*(): (ptr CReturn[CFr]) {.importc: "ffi_generate_random_fr".} + +proc ffi_generate_stealth_commitment*(viewing_public_key: ptr CG1Projective, + spending_public_key: ptr CG1Projective, + ephemeral_private_key: ptr CFr): (ptr CReturn[CStealthCommitment]) {.importc: "ffi_generate_stealth_commitment".} + +proc ffi_generate_stealth_private_key*(ephemeral_public_key: ptr CG1Projective, + spending_key: ptr CFr, + viewing_key: ptr CFr, + view_tag: ptr uint64): (ptr CReturn[CFr]) {.importc: "ffi_generate_stealth_private_key".} + +proc ffi_random_keypair*(): (ptr CReturn[CKeyPair]) {.importc: "ffi_random_keypair".} + + +## Nim wrappers and types for the ERC-5564-BN254 module + +type FFIResult[T] = Result[T, string] +type Fr = array[32, uint8] +type G1Projective = array[32, uint8] +type KeyPair* = object + private_key*: Fr + public_key*: G1Projective +type StealthCommitment* = object + stealth_commitment*: G1Projective + view_tag*: uint64 +type PrivateKey* = Fr +type PublicKey* = G1Projective + +proc generateRandomFr*(): FFIResult[Fr] = + let res_ptr = (ffi_generate_random_fr()) + let res_value = res_ptr[] + if res_value.err_code != 0: + drop_ffi_generate_random_fr(res_ptr) + return err("Error generating random field element: " & $res_value.err_code) + + let ret = res_value.value.x0 + drop_ffi_generate_random_fr(res_ptr) + return ok(ret) + +proc generateKeypair*(): FFIResult[KeyPair] = + let res_ptr = (ffi_random_keypair()) + let res_value = res_ptr[] + if res_value.err_code != 0: + drop_ffi_random_keypair(res_ptr) + return err("Error generating random keypair: " & $res_value.err_code) + + let ret = KeyPair(private_key: res_value.value.private_key.x0, public_key: res_value.value.public_key.x0) + drop_ffi_random_keypair(res_ptr) + return ok(ret) + +proc generateStealthCommitment*(viewing_public_key: G1Projective, + spending_public_key: G1Projective, + ephemeral_private_key: Fr): FFIResult[StealthCommitment] = + let viewing_public_key = CG1Projective(x0: viewing_public_key) + let viewing_public_key_ptr = unsafeAddr(viewing_public_key) + let spending_public_key = CG1Projective(x0: spending_public_key) + let spending_public_key_ptr = unsafeAddr(spending_public_key) + let ephemeral_private_key = CFr(x0: ephemeral_private_key) + let ephemeral_private_key_ptr = unsafeAddr(ephemeral_private_key) + + let res_ptr = (ffi_generate_stealth_commitment(viewing_public_key_ptr, spending_public_key_ptr, ephemeral_private_key_ptr)) + let res_value = res_ptr[] + if res_value.err_code != 0: + drop_ffi_generate_stealth_commitment(res_ptr) + return err("Error generating stealth commitment: " & $res_value.err_code) + + let ret = StealthCommitment(stealth_commitment: res_value.value.stealth_commitment.x0, view_tag: res_value.value.view_tag) + drop_ffi_generate_stealth_commitment(res_ptr) + return ok(ret) + +proc generateStealthPrivateKey*(ephemeral_public_key: G1Projective, + spending_key: Fr, + viewing_key: Fr, + view_tag: uint64): FFIResult[Fr] = + let ephemeral_public_key = CG1Projective(x0: ephemeral_public_key) + let ephemeral_public_key_ptr = unsafeAddr(ephemeral_public_key) + let spending_key = CFr(x0: spending_key) + let spending_key_ptr = unsafeAddr(spending_key) + let viewing_key = CFr(x0: viewing_key) + let viewing_key_ptr = unsafeAddr(viewing_key) + let view_tag_ptr = unsafeAddr(view_tag) + + let res_ptr = (ffi_generate_stealth_private_key(ephemeral_public_key_ptr, spending_key_ptr, viewing_key_ptr, view_tag_ptr)) + let res_value = res_ptr[] + if res_value.err_code != 0: + drop_ffi_generate_stealth_private_key(res_ptr) + return err("Error generating stealth private key: " & $res_value.err_code) + + let ret = res_value.value.x0 + drop_ffi_generate_stealth_private_key(res_ptr) + return ok(ret) diff --git a/examples/wakustealthcommitments/nim.cfg b/examples/wakustealthcommitments/nim.cfg new file mode 100644 index 0000000000..fd55a3e995 --- /dev/null +++ b/examples/wakustealthcommitments/nim.cfg @@ -0,0 +1,10 @@ +-d:chronicles_line_numbers +-d:discv5_protocol_id="d5waku" +-d:chronicles_runtime_filtering=on +-d:chronicles_sinks="textlines,json" +-d:chronicles_default_output_device=dynamic +# Disabling the following topics from nim-eth and nim-dnsdisc since some types cannot be serialized +-d:chronicles_disabled_topics="eth,dnsdisc.client" +-d:chronicles_log_level=INFO +# Results in empty output for some reason +#-d:"chronicles_enabled_topics=GossipSub:TRACE,WakuRelay:TRACE" diff --git a/examples/wakustealthcommitments/node_spec.nim b/examples/wakustealthcommitments/node_spec.nim new file mode 100644 index 0000000000..d2b1e81b01 --- /dev/null +++ b/examples/wakustealthcommitments/node_spec.nim @@ -0,0 +1,106 @@ +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +import ../../apps/wakunode2/[networks_config, app, external_config] +import ../../waku/common/logging +import + std/[options, strutils, os, sequtils], + stew/shims/net as stewNet, + chronicles, + chronos, + metrics, + libbacktrace, + libp2p/crypto/crypto + +export + networks_config, + app, + logging, + options, + strutils, + os, + sequtils, + stewNet, + chronicles, + chronos, + metrics, + libbacktrace, + crypto + +proc setup*(): App = + const versionString = "version / git commit hash: " & app.git_version + let rng = crypto.newRng() + + let confRes = WakuNodeConf.load(version = versionString) + if confRes.isErr(): + error "failure while loading the configuration", error = $confRes.error + quit(QuitFailure) + + var conf = confRes.get() + + let twnClusterConf = ClusterConf.TheWakuNetworkConf() + if len(conf.shards) != 0: + conf.pubsubTopics = conf.shards.mapIt(twnClusterConf.pubsubTopics[it.uint16]) + else: + conf.pubsubTopics = twnClusterConf.pubsubTopics + + # Override configuration + conf.maxMessageSize = twnClusterConf.maxMessageSize + conf.clusterId = twnClusterConf.clusterId + conf.rlnRelay = twnClusterConf.rlnRelay + conf.rlnRelayEthContractAddress = twnClusterConf.rlnRelayEthContractAddress + conf.rlnRelayDynamic = twnClusterConf.rlnRelayDynamic + conf.rlnRelayBandwidthThreshold = twnClusterConf.rlnRelayBandwidthThreshold + conf.discv5Discovery = twnClusterConf.discv5Discovery + conf.discv5BootstrapNodes = + conf.discv5BootstrapNodes & twnClusterConf.discv5BootstrapNodes + conf.rlnEpochSizeSec = twnClusterConf.rlnEpochSizeSec + conf.rlnRelayUserMessageLimit = twnClusterConf.rlnRelayUserMessageLimit + + var wakunode2 = App.init(rng, conf) + ## Peer persistence + let res1 = wakunode2.setupPeerPersistence() + if res1.isErr(): + error "1/5 Setting up storage failed", error = $res1.error + quit(QuitFailure) + + debug "2/5 Retrieve dynamic bootstrap nodes" + + let res3 = wakunode2.setupDyamicBootstrapNodes() + if res3.isErr(): + error "2/5 Retrieving dynamic bootstrap nodes failed", error = $res3.error + quit(QuitFailure) + + debug "3/5 Initializing node" + + let res4 = wakunode2.setupWakuApp() + if res4.isErr(): + error "3/5 Initializing node failed", error = $res4.error + quit(QuitFailure) + + debug "4/5 Mounting protocols" + + var res5: Result[void, string] + try: + res5 = waitFor wakunode2.setupAndMountProtocols() + if res5.isErr(): + error "4/5 Mounting protocols failed", error = $res5.error + quit(QuitFailure) + except Exception: + error "4/5 Mounting protocols failed", error = getCurrentExceptionMsg() + quit(QuitFailure) + + debug "5/5 Starting node and mounted protocols" + + # set triggerSelf to false, we don't want to process our own stealthCommitments + wakunode2.node.wakuRelay.triggerSelf = false + + let res6 = wakunode2.startApp() + if res6.isErr(): + error "5/5 Starting node and protocols failed", error = $res6.error + quit(QuitFailure) + + info "Node setup complete" + return wakunode2 diff --git a/examples/wakustealthcommitments/stealth_commitment_protocol.nim b/examples/wakustealthcommitments/stealth_commitment_protocol.nim new file mode 100644 index 0000000000..c9cf0accee --- /dev/null +++ b/examples/wakustealthcommitments/stealth_commitment_protocol.nim @@ -0,0 +1,154 @@ +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +import + stew/results, + ../../waku/common/logging, + ../../waku/waku_node, + ../../waku/waku_rln_relay, + ./erc_5564_interface as StealthCommitmentFFI, + ./node_spec, + ./wire_spec + +export + wire_spec, + logging + +type StealthCommitmentProtocol* = object + wakuApp: App + contentTopic: string + spendingKeyPair: StealthCommitmentFFI.KeyPair + viewingKeyPair: StealthCommitmentFFI.KeyPair + +proc deserialize(T: type StealthCommitmentFFI.PublicKey, v: SerializedKey): Result[T, string] = + # deserialize seq[byte] into array[32, uint8] + if v.len != 32: + return err("invalid key length") + var buf: array[32, uint8] + for i in 0.. 0: some(spendingPubKey) else: none(SerializedKey) + var viewingPubKey = newSeq[byte]() + discard ? pb.getField(3, viewingPubKey) + msg.viewingPubKey = if viewingPubKey.len > 0: some(viewingPubKey) else: none(SerializedKey) + + if msg.spendingPubKey.isSome() and msg.viewingPubKey.isSome(): + msg.stealthCommitment = none(SerializedKey) + msg.viewTag = none(uint64) + return ok(msg) + if msg.spendingPubKey.isSome() and msg.viewingPubKey.isNone(): + return err(ProtoError.RequiredFieldMissing) + if msg.spendingPubKey.isNone() and msg.viewingPubKey.isSome(): + return err(ProtoError.RequiredFieldMissing) + if msg.request == true and msg.spendingPubKey.isNone() and msg.viewingPubKey.isNone(): + return err(ProtoError.RequiredFieldMissing) + + + var stealthCommitment = newSeq[byte]() + discard ? pb.getField(4, stealthCommitment) + msg.stealthCommitment = if stealthCommitment.len > 0: some(stealthCommitment) else: none(SerializedKey) + + var ephemeralPubKey = newSeq[byte]() + discard ? pb.getField(5, ephemeralPubKey) + msg.ephemeralPubKey = if ephemeralPubKey.len > 0: some(ephemeralPubKey) else: none(SerializedKey) + + var viewTag: uint64 + discard ? pb.getField(6, viewTag) + msg.viewTag = if viewTag != 0: some(viewTag) else: none(uint64) + + if msg.stealthCommitment.isNone() and msg.viewTag.isNone() and msg.ephemeralPubKey.isNone(): + return err(ProtoError.RequiredFieldMissing) + + if msg.stealthCommitment.isSome() and msg.viewTag.isNone(): + return err(ProtoError.RequiredFieldMissing) + + if msg.stealthCommitment.isNone() and msg.viewTag.isSome(): + return err(ProtoError.RequiredFieldMissing) + + if msg.stealthCommitment.isSome() and msg.viewTag.isSome(): + msg.spendingPubKey = none(SerializedKey) + msg.viewingPubKey = none(SerializedKey) + + ok(msg) + +proc encode*(msg: WakuStealthCommitmentMsg): ProtoBuffer = + var serialised = initProtoBuffer() + + serialised.write(1, uint64(msg.request)) + + if msg.spendingPubKey.isSome(): + serialised.write(2, msg.spendingPubKey.get()) + if msg.viewingPubKey.isSome(): + serialised.write(3, msg.viewingPubKey.get()) + if msg.stealthCommitment.isSome(): + serialised.write(4, msg.stealthCommitment.get()) + if msg.ephemeralPubKey.isSome(): + serialised.write(5, msg.ephemeralPubKey.get()) + if msg.viewTag.isSome(): + serialised.write(6, msg.viewTag.get()) + + return serialised + +func toByteSeq*(str: string): seq[byte] {.inline.} = + ## Converts a string to the corresponding byte sequence. + @(str.toOpenArrayByte(0, str.high)) + +proc constructRequest*(spendingPubKey: SerializedKey, viewingPubKey: SerializedKey): WakuStealthCommitmentMsg = + WakuStealthCommitmentMsg(request: true, spendingPubKey: some(spendingPubKey), viewingPubKey: some(viewingPubKey)) + +proc constructResponse*(stealthCommitment: SerializedKey, ephemeralPubKey: SerializedKey, viewTag: uint64): WakuStealthCommitmentMsg = + WakuStealthCommitmentMsg(request: false, stealthCommitment: some(stealthCommitment), ephemeralPubKey: some(ephemeralPubKey), viewTag: some(viewTag)) \ No newline at end of file