mirror of https://github.com/waku-org/nwaku.git
fix(rln-relay): modify keystore credentials logic (#1956)
* fix(rln-relay): modify keystore credentials logic fix: bump version * Update waku/waku_rln_relay/group_manager/on_chain/group_manager.nim Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com> * Update tests/waku_rln_relay/test_waku_rln_relay.nim Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com> * Update waku/waku_keystore/protocol_types.nim Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com> * fix: greatly improve error handling * fix: display proc and appropriate assert --------- Co-authored-by: Ivan Folgueira Bande <128452529+Ivansete-status@users.noreply.github.com>
This commit is contained in:
parent
ac25855018
commit
e7b2b88f5b
|
@ -508,11 +508,10 @@ proc processInput(rfd: AsyncFD, rng: ref HmacDrbgContext) {.async.} =
|
||||||
let rlnConf = WakuRlnConfig(
|
let rlnConf = WakuRlnConfig(
|
||||||
rlnRelayDynamic: conf.rlnRelayDynamic,
|
rlnRelayDynamic: conf.rlnRelayDynamic,
|
||||||
rlnRelayCredIndex: conf.rlnRelayCredIndex,
|
rlnRelayCredIndex: conf.rlnRelayCredIndex,
|
||||||
rlnRelayMembershipGroupIndex: conf.rlnRelayMembershipGroupIndex,
|
|
||||||
rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress,
|
rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress,
|
||||||
rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress,
|
rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress,
|
||||||
rlnRelayCredPath: conf.rlnRelayCredPath,
|
rlnRelayCredPath: conf.rlnRelayCredPath,
|
||||||
rlnRelayCredentialsPassword: conf.rlnRelayCredentialsPassword
|
rlnRelayCredPassword: conf.rlnRelayCredPassword
|
||||||
)
|
)
|
||||||
|
|
||||||
waitFor node.mountRlnRelay(rlnConf,
|
waitFor node.mountRlnRelay(rlnConf,
|
||||||
|
|
|
@ -237,11 +237,6 @@ type
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
name: "rln-relay-cred-index" }: uint
|
name: "rln-relay-cred-index" }: uint
|
||||||
|
|
||||||
rlnRelayMembershipGroupIndex* {.
|
|
||||||
desc: "the index of credentials to use, within a specific rln membership set",
|
|
||||||
defaultValue: 0
|
|
||||||
name: "rln-relay-membership-group-index" }: uint
|
|
||||||
|
|
||||||
rlnRelayDynamic* {.
|
rlnRelayDynamic* {.
|
||||||
desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false",
|
desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false",
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
|
@ -267,7 +262,7 @@ type
|
||||||
defaultValue: ""
|
defaultValue: ""
|
||||||
name: "rln-relay-eth-contract-address" }: string
|
name: "rln-relay-eth-contract-address" }: string
|
||||||
|
|
||||||
rlnRelayCredentialsPassword* {.
|
rlnRelayCredPassword* {.
|
||||||
desc: "Password for encrypting RLN credentials",
|
desc: "Password for encrypting RLN credentials",
|
||||||
defaultValue: ""
|
defaultValue: ""
|
||||||
name: "rln-relay-cred-password" }: string
|
name: "rln-relay-cred-password" }: string
|
||||||
|
|
|
@ -398,11 +398,10 @@ proc setupProtocols(node: WakuNode,
|
||||||
let rlnConf = WakuRlnConfig(
|
let rlnConf = WakuRlnConfig(
|
||||||
rlnRelayDynamic: conf.rlnRelayDynamic,
|
rlnRelayDynamic: conf.rlnRelayDynamic,
|
||||||
rlnRelayCredIndex: conf.rlnRelayCredIndex,
|
rlnRelayCredIndex: conf.rlnRelayCredIndex,
|
||||||
rlnRelayMembershipGroupIndex: conf.rlnRelayMembershipGroupIndex,
|
|
||||||
rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress,
|
rlnRelayEthContractAddress: conf.rlnRelayEthContractAddress,
|
||||||
rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress,
|
rlnRelayEthClientAddress: conf.rlnRelayEthClientAddress,
|
||||||
rlnRelayCredPath: conf.rlnRelayCredPath,
|
rlnRelayCredPath: conf.rlnRelayCredPath,
|
||||||
rlnRelayCredentialsPassword: conf.rlnRelayCredentialsPassword,
|
rlnRelayCredPassword: conf.rlnRelayCredPassword,
|
||||||
rlnRelayTreePath: conf.rlnRelayTreePath,
|
rlnRelayTreePath: conf.rlnRelayTreePath,
|
||||||
rlnRelayBandwidthThreshold: conf.rlnRelayBandwidthThreshold
|
rlnRelayBandwidthThreshold: conf.rlnRelayBandwidthThreshold
|
||||||
)
|
)
|
||||||
|
|
|
@ -150,11 +150,6 @@ type
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
name: "rln-relay-membership-index" }: uint
|
name: "rln-relay-membership-index" }: uint
|
||||||
|
|
||||||
rlnRelayMembershipGroupIndex* {.
|
|
||||||
desc: "the index of credentials to use, within a specific rln membership set",
|
|
||||||
defaultValue: 0
|
|
||||||
name: "rln-relay-membership-group-index" }: uint
|
|
||||||
|
|
||||||
rlnRelayDynamic* {.
|
rlnRelayDynamic* {.
|
||||||
desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false",
|
desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false",
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
|
@ -180,7 +175,7 @@ type
|
||||||
defaultValue: ""
|
defaultValue: ""
|
||||||
name: "rln-relay-eth-contract-address" }: string
|
name: "rln-relay-eth-contract-address" }: string
|
||||||
|
|
||||||
rlnRelayCredentialsPassword* {.
|
rlnRelayCredPassword* {.
|
||||||
desc: "Password for encrypting RLN credentials",
|
desc: "Password for encrypting RLN credentials",
|
||||||
defaultValue: ""
|
defaultValue: ""
|
||||||
name: "rln-relay-cred-password" }: string
|
name: "rln-relay-cred-password" }: string
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
{.used.}
|
{.used.}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[algorithm, json, options, os],
|
std/[os, json],
|
||||||
testutils/unittests, chronos, stint
|
chronos,
|
||||||
|
testutils/unittests
|
||||||
import
|
import
|
||||||
../../waku/waku_keystore,
|
../../waku/waku_keystore,
|
||||||
./testlib/common
|
./testlib/common
|
||||||
|
@ -44,7 +45,7 @@ procSuite "Credentials test suite":
|
||||||
keystore["appIdentifier"].getStr() == testAppInfo.appIdentifier
|
keystore["appIdentifier"].getStr() == testAppInfo.appIdentifier
|
||||||
keystore["version"].getStr() == testAppInfo.version
|
keystore["version"].getStr() == testAppInfo.version
|
||||||
# We assume the loaded keystore to not have credentials set (previous tests delete the keystore at filepath)
|
# We assume the loaded keystore to not have credentials set (previous tests delete the keystore at filepath)
|
||||||
keystore["credentials"].getElems().len() == 0
|
keystore["credentials"].len() == 0
|
||||||
|
|
||||||
test "Add credentials to keystore":
|
test "Add credentials to keystore":
|
||||||
|
|
||||||
|
@ -61,30 +62,15 @@ procSuite "Credentials test suite":
|
||||||
var idCredential = IdentityCredential(idTrapdoor: idTrapdoor, idNullifier: idNullifier, idSecretHash: idSecretHash, idCommitment: idCommitment)
|
var idCredential = IdentityCredential(idTrapdoor: idTrapdoor, idNullifier: idNullifier, idSecretHash: idSecretHash, idCommitment: idCommitment)
|
||||||
|
|
||||||
var contract = MembershipContract(chainId: "5", address: "0x0123456789012345678901234567890123456789")
|
var contract = MembershipContract(chainId: "5", address: "0x0123456789012345678901234567890123456789")
|
||||||
var index1 = MembershipIndex(1)
|
var index = MembershipIndex(1)
|
||||||
var membershipGroup1 = MembershipGroup(membershipContract: contract, treeIndex: index1)
|
|
||||||
|
|
||||||
let membershipCredentials1 = MembershipCredentials(identityCredential: idCredential,
|
|
||||||
membershipGroups: @[membershipGroup1])
|
|
||||||
|
|
||||||
# We generate a random identity credential (inter-value constrains are not enforced, otherwise we need to load e.g. zerokit RLN keygen)
|
|
||||||
idTrapdoor = randomSeqByte(rng[], 32)
|
|
||||||
idNullifier = randomSeqByte(rng[], 32)
|
|
||||||
idSecretHash = randomSeqByte(rng[], 32)
|
|
||||||
idCommitment = randomSeqByte(rng[], 32)
|
|
||||||
|
|
||||||
idCredential = IdentityCredential(idTrapdoor: idTrapdoor, idNullifier: idNullifier, idSecretHash: idSecretHash, idCommitment: idCommitment)
|
|
||||||
|
|
||||||
var index2 = MembershipIndex(2)
|
|
||||||
var membershipGroup2 = MembershipGroup(membershipContract: contract, treeIndex: index2)
|
|
||||||
|
|
||||||
let membershipCredentials2 = MembershipCredentials(identityCredential: idCredential,
|
|
||||||
membershipGroups: @[membershipGroup2])
|
|
||||||
|
|
||||||
|
let membershipCredential = KeystoreMembership(membershipContract: contract,
|
||||||
|
treeIndex: index,
|
||||||
|
identityCredential: idCredential)
|
||||||
let password = "%m0um0ucoW%"
|
let password = "%m0um0ucoW%"
|
||||||
|
|
||||||
let keystoreRes = addMembershipCredentials(path = filepath,
|
let keystoreRes = addMembershipCredentials(path = filepath,
|
||||||
credentials = @[membershipCredentials1, membershipCredentials2],
|
membership = membershipCredential,
|
||||||
password = password,
|
password = password,
|
||||||
appInfo = testAppInfo)
|
appInfo = testAppInfo)
|
||||||
|
|
||||||
|
@ -98,47 +84,25 @@ procSuite "Credentials test suite":
|
||||||
|
|
||||||
# We generate two random identity credentials (inter-value constrains are not enforced, otherwise we need to load e.g. zerokit RLN keygen)
|
# We generate two random identity credentials (inter-value constrains are not enforced, otherwise we need to load e.g. zerokit RLN keygen)
|
||||||
var
|
var
|
||||||
idTrapdoor1 = randomSeqByte(rng[], 32)
|
idTrapdoor = randomSeqByte(rng[], 32)
|
||||||
idNullifier1 = randomSeqByte(rng[], 32)
|
idNullifier = randomSeqByte(rng[], 32)
|
||||||
idSecretHash1 = randomSeqByte(rng[], 32)
|
idSecretHash = randomSeqByte(rng[], 32)
|
||||||
idCommitment1 = randomSeqByte(rng[], 32)
|
idCommitment = randomSeqByte(rng[], 32)
|
||||||
idCredential1 = IdentityCredential(idTrapdoor: idTrapdoor1, idNullifier: idNullifier1, idSecretHash: idSecretHash1, idCommitment: idCommitment1)
|
idCredential = IdentityCredential(idTrapdoor: idTrapdoor, idNullifier: idNullifier, idSecretHash: idSecretHash, idCommitment: idCommitment)
|
||||||
|
|
||||||
var
|
|
||||||
idTrapdoor2 = randomSeqByte(rng[], 32)
|
|
||||||
idNullifier2 = randomSeqByte(rng[], 32)
|
|
||||||
idSecretHash2 = randomSeqByte(rng[], 32)
|
|
||||||
idCommitment2 = randomSeqByte(rng[], 32)
|
|
||||||
idCredential2 = IdentityCredential(idTrapdoor: idTrapdoor2, idNullifier: idNullifier2, idSecretHash: idSecretHash2, idCommitment: idCommitment2)
|
|
||||||
|
|
||||||
# We generate two distinct membership groups
|
# We generate two distinct membership groups
|
||||||
var contract1 = MembershipContract(chainId: "5", address: "0x0123456789012345678901234567890123456789")
|
var contract = MembershipContract(chainId: "5", address: "0x0123456789012345678901234567890123456789")
|
||||||
var index1 = MembershipIndex(1)
|
var index = MembershipIndex(1)
|
||||||
var membershipGroup1 = MembershipGroup(membershipContract: contract1, treeIndex: index1)
|
var membershipCredential = KeystoreMembership(membershipContract: contract,
|
||||||
|
treeIndex: index,
|
||||||
|
identityCredential: idCredential)
|
||||||
|
|
||||||
var contract2 = MembershipContract(chainId: "6", address: "0x0000000000000000000000000000000000000000")
|
|
||||||
var index2 = MembershipIndex(2)
|
|
||||||
var membershipGroup2 = MembershipGroup(membershipContract: contract2, treeIndex: index2)
|
|
||||||
|
|
||||||
# We generate three membership credentials
|
|
||||||
let membershipCredentials1 = MembershipCredentials(identityCredential: idCredential1,
|
|
||||||
membershipGroups: @[membershipGroup1])
|
|
||||||
|
|
||||||
let membershipCredentials2 = MembershipCredentials(identityCredential: idCredential2,
|
|
||||||
membershipGroups: @[membershipGroup2])
|
|
||||||
|
|
||||||
let membershipCredentials3 = MembershipCredentials(identityCredential: idCredential1,
|
|
||||||
membershipGroups: @[membershipGroup2])
|
|
||||||
|
|
||||||
# This is the same as rlnMembershipCredentials3, should not change the keystore entry of idCredential
|
|
||||||
let membershipCredentials4 = MembershipCredentials(identityCredential: idCredential1,
|
|
||||||
membershipGroups: @[membershipGroup2])
|
|
||||||
|
|
||||||
let password = "%m0um0ucoW%"
|
let password = "%m0um0ucoW%"
|
||||||
|
|
||||||
# We add credentials to the keystore. Note that only 3 credentials should be effectively added, since rlnMembershipCredentials3 is equal to membershipCredentials2
|
# We add credentials to the keystore. Note that only 3 credentials should be effectively added, since rlnMembershipCredentials3 is equal to membershipCredentials2
|
||||||
let keystoreRes = addMembershipCredentials(path = filepath,
|
let keystoreRes = addMembershipCredentials(path = filepath,
|
||||||
credentials = @[membershipCredentials1, membershipCredentials2, membershipCredentials3, membershipCredentials4],
|
membership = membershipCredential,
|
||||||
password = password,
|
password = password,
|
||||||
appInfo = testAppInfo)
|
appInfo = testAppInfo)
|
||||||
|
|
||||||
|
@ -146,45 +110,16 @@ procSuite "Credentials test suite":
|
||||||
keystoreRes.isOk()
|
keystoreRes.isOk()
|
||||||
|
|
||||||
# We test retrieval of credentials.
|
# We test retrieval of credentials.
|
||||||
var expectedMembershipGroups1 = @[membershipGroup1, membershipGroup2]
|
var expectedMembership = membershipCredential
|
||||||
expectedMembershipGroups1.sort(sortMembershipGroup)
|
let membershipQuery = KeystoreMembership(membershipContract: contract,
|
||||||
let expectedCredential1 = MembershipCredentials(identityCredential: idCredential1,
|
treeIndex: index)
|
||||||
membershipGroups: expectedMembershipGroups1)
|
|
||||||
|
|
||||||
|
|
||||||
var expectedMembershipGroups2 = @[membershipGroup2]
|
|
||||||
expectedMembershipGroups2.sort(sortMembershipGroup)
|
|
||||||
let expectedCredential2 = MembershipCredentials(identityCredential: idCredential2,
|
|
||||||
membershipGroups: expectedMembershipGroups2)
|
|
||||||
|
|
||||||
|
|
||||||
# We retrieve all credentials stored under password (no filter)
|
|
||||||
var recoveredCredentialsRes = getMembershipCredentials(path = filepath,
|
var recoveredCredentialsRes = getMembershipCredentials(path = filepath,
|
||||||
password = password,
|
password = password,
|
||||||
|
query = membershipQuery,
|
||||||
appInfo = testAppInfo)
|
appInfo = testAppInfo)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
recoveredCredentialsRes.isOk()
|
recoveredCredentialsRes.isOk()
|
||||||
recoveredCredentialsRes.get() == @[expectedCredential1, expectedCredential2]
|
recoveredCredentialsRes.get() == expectedMembership
|
||||||
|
|
||||||
|
|
||||||
# We retrieve credentials by filtering on an IdentityCredential
|
|
||||||
recoveredCredentialsRes = getMembershipCredentials(path = filepath,
|
|
||||||
password = password,
|
|
||||||
filterIdentityCredentials = @[idCredential1],
|
|
||||||
appInfo = testAppInfo)
|
|
||||||
|
|
||||||
check:
|
|
||||||
recoveredCredentialsRes.isOk()
|
|
||||||
recoveredCredentialsRes.get() == @[expectedCredential1]
|
|
||||||
|
|
||||||
# We retrieve credentials by filtering on multiple IdentityCredentials
|
|
||||||
recoveredCredentialsRes = getMembershipCredentials(path = filepath,
|
|
||||||
password = password,
|
|
||||||
filterIdentityCredentials = @[idCredential1, idCredential2],
|
|
||||||
appInfo = testAppInfo)
|
|
||||||
|
|
||||||
check:
|
|
||||||
recoveredCredentialsRes.isOk()
|
|
||||||
recoveredCredentialsRes.get() == @[expectedCredential1, expectedCredential2]
|
|
||||||
|
|
||||||
|
|
|
@ -815,10 +815,14 @@ suite "Waku rln relay":
|
||||||
|
|
||||||
let index = MembershipIndex(1)
|
let index = MembershipIndex(1)
|
||||||
|
|
||||||
let rlnMembershipContract = MembershipContract(chainId: "5", address: "0x0123456789012345678901234567890123456789")
|
let keystoreMembership = KeystoreMembership(
|
||||||
let rlnMembershipGroup = MembershipGroup(membershipContract: rlnMembershipContract, treeIndex: index)
|
membershipContract: MembershipContract(
|
||||||
let rlnMembershipCredentials = MembershipCredentials(identityCredential: idCredential, membershipGroups: @[rlnMembershipGroup])
|
chainId: "5",
|
||||||
|
address: "0x0123456789012345678901234567890123456789"
|
||||||
|
),
|
||||||
|
treeIndex: index,
|
||||||
|
identityCredential: idCredential,
|
||||||
|
)
|
||||||
let password = "%m0um0ucoW%"
|
let password = "%m0um0ucoW%"
|
||||||
|
|
||||||
let filepath = "./testRLNCredentials.txt"
|
let filepath = "./testRLNCredentials.txt"
|
||||||
|
@ -827,30 +831,29 @@ suite "Waku rln relay":
|
||||||
# Write RLN credentials
|
# Write RLN credentials
|
||||||
require:
|
require:
|
||||||
addMembershipCredentials(path = filepath,
|
addMembershipCredentials(path = filepath,
|
||||||
credentials = @[rlnMembershipCredentials],
|
membership = keystoreMembership,
|
||||||
password = password,
|
password = password,
|
||||||
appInfo = RLNAppInfo).isOk()
|
appInfo = RLNAppInfo).isOk()
|
||||||
|
|
||||||
let readCredentialsResult = getMembershipCredentials(path = filepath,
|
let readKeystoreRes = getMembershipCredentials(path = filepath,
|
||||||
password = password,
|
password = password,
|
||||||
filterMembershipContracts = @[rlnMembershipContract],
|
# here the query would not include
|
||||||
|
# the identityCredential,
|
||||||
|
# since it is not part of the query
|
||||||
|
# but have used the same value
|
||||||
|
# to avoid re-declaration
|
||||||
|
query = keystoreMembership,
|
||||||
appInfo = RLNAppInfo)
|
appInfo = RLNAppInfo)
|
||||||
|
assert readKeystoreRes.isOk(), $readKeystoreRes.error
|
||||||
|
|
||||||
require:
|
# getMembershipCredentials returns the credential in the keystore which matches
|
||||||
readCredentialsResult.isOk()
|
# the query, in this case the query is =
|
||||||
|
# chainId = "5" and
|
||||||
# getMembershipCredentials returns all credentials in keystore as sequence matching the filter
|
# address = "0x0123456789012345678901234567890123456789" and
|
||||||
let allMatchingCredentials = readCredentialsResult.get()
|
# treeIndex = 1
|
||||||
# if any is found, we return the first credential, otherwise credentials is none
|
let readKeystoreMembership = readKeystoreRes.get()
|
||||||
var credentials = none(MembershipCredentials)
|
|
||||||
if allMatchingCredentials.len() > 0:
|
|
||||||
credentials = some(allMatchingCredentials[0])
|
|
||||||
|
|
||||||
require:
|
|
||||||
credentials.isSome()
|
|
||||||
check:
|
check:
|
||||||
credentials.get().identityCredential == idCredential
|
readKeystoreMembership == keystoreMembership
|
||||||
credentials.get().membershipGroups == @[rlnMembershipGroup]
|
|
||||||
|
|
||||||
test "histogram static bucket generation":
|
test "histogram static bucket generation":
|
||||||
let buckets = generateBucketsForHistogram(10)
|
let buckets = generateBucketsForHistogram(10)
|
||||||
|
|
|
@ -79,19 +79,17 @@ when isMainModule:
|
||||||
debug "Transaction hash", txHash = groupManager.registrationTxHash.get()
|
debug "Transaction hash", txHash = groupManager.registrationTxHash.get()
|
||||||
|
|
||||||
# 6. write to keystore
|
# 6. write to keystore
|
||||||
let keystoreCred = MembershipCredentials(
|
let keystoreCred = KeystoreMembership(
|
||||||
|
membershipContract: MembershipContract(
|
||||||
|
chainId: $groupManager.chainId.get(),
|
||||||
|
address: conf.rlnRelayEthContractAddress,
|
||||||
|
),
|
||||||
|
treeIndex: groupManager.membershipIndex.get(),
|
||||||
identityCredential: credential,
|
identityCredential: credential,
|
||||||
membershipGroups: @[MembershipGroup(
|
|
||||||
membershipContract: MembershipContract(
|
|
||||||
chainId: $groupManager.chainId.get(),
|
|
||||||
address: conf.rlnRelayEthContractAddress,
|
|
||||||
),
|
|
||||||
treeIndex: groupManager.membershipIndex.get(),
|
|
||||||
)]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
let persistRes = addMembershipCredentials(conf.rlnRelayCredPath,
|
let persistRes = addMembershipCredentials(conf.rlnRelayCredPath,
|
||||||
@[keystoreCred],
|
keystoreCred,
|
||||||
conf.rlnRelayCredPassword,
|
conf.rlnRelayCredPassword,
|
||||||
RLNAppInfo)
|
RLNAppInfo)
|
||||||
if persistRes.isErr():
|
if persistRes.isErr():
|
||||||
|
|
|
@ -8,22 +8,24 @@ import
|
||||||
stew/[results, byteutils],
|
stew/[results, byteutils],
|
||||||
./protocol_types
|
./protocol_types
|
||||||
|
|
||||||
# Encodes a Membership credential to a byte sequence
|
# Encodes a KeystoreMembership credential to a byte sequence
|
||||||
proc encode*(credential: MembershipCredentials): seq[byte] =
|
proc encode*(credential: KeystoreMembership): seq[byte] =
|
||||||
# TODO: use custom encoding, avoid wordy json
|
# TODO: use custom encoding, avoid wordy json
|
||||||
var stringCredential: string
|
var stringCredential: string
|
||||||
# NOTE: toUgly appends to the string, doesn't replace its contents
|
# NOTE: toUgly appends to the string, doesn't replace its contents
|
||||||
stringCredential.toUgly(%credential)
|
stringCredential.toUgly(%credential)
|
||||||
return toBytes(stringCredential)
|
return toBytes(stringCredential)
|
||||||
|
|
||||||
# Decodes a byte sequence to a Membership credential
|
# Decodes a byte sequence to a KeystoreMembership credential
|
||||||
proc decode*(encodedCredential: seq[byte]): KeystoreResult[MembershipCredentials] =
|
proc decode*(encodedCredential: seq[byte]): KeystoreResult[KeystoreMembership] =
|
||||||
# TODO: use custom decoding, avoid wordy json
|
# TODO: use custom decoding, avoid wordy json
|
||||||
try:
|
try:
|
||||||
# we parse the json decrypted keystoreCredential
|
# we parse the json decrypted keystoreCredential
|
||||||
let jsonObject = parseJson(string.fromBytes(encodedCredential))
|
let jsonObject = parseJson(string.fromBytes(encodedCredential))
|
||||||
return ok(to(jsonObject, MembershipCredentials))
|
return ok(to(jsonObject, KeystoreMembership))
|
||||||
except JsonParsingError:
|
except JsonParsingError:
|
||||||
return err(KeystoreJsonError)
|
return err(AppKeystoreError(kind: KeystoreJsonError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
except Exception: #parseJson raises Exception
|
except Exception: #parseJson raises Exception
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
|
|
|
@ -5,7 +5,7 @@ else:
|
||||||
|
|
||||||
import
|
import
|
||||||
options, json, strutils,
|
options, json, strutils,
|
||||||
std/[algorithm, os, sequtils, sets]
|
std/[tables, os]
|
||||||
|
|
||||||
import
|
import
|
||||||
./keyfile,
|
./keyfile,
|
||||||
|
@ -20,15 +20,16 @@ proc createAppKeystore*(path: string,
|
||||||
|
|
||||||
let keystore = AppKeystore(application: appInfo.application,
|
let keystore = AppKeystore(application: appInfo.application,
|
||||||
appIdentifier: appInfo.appIdentifier,
|
appIdentifier: appInfo.appIdentifier,
|
||||||
credentials: @[],
|
version: appInfo.version,
|
||||||
version: appInfo.version)
|
credentials: initTable[string, KeystoreMembership]())
|
||||||
|
|
||||||
var jsonKeystore: string
|
var jsonKeystore: string
|
||||||
jsonKeystore.toUgly(%keystore)
|
jsonKeystore.toUgly(%keystore)
|
||||||
|
|
||||||
var f: File
|
var f: File
|
||||||
if not f.open(path, fmWrite):
|
if not f.open(path, fmWrite):
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: "Cannot open file for writing"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# To avoid other users/attackers to be able to read keyfiles, we make the file readable/writable only by the running user
|
# To avoid other users/attackers to be able to read keyfiles, we make the file readable/writable only by the running user
|
||||||
|
@ -38,7 +39,8 @@ proc createAppKeystore*(path: string,
|
||||||
f.write(separator)
|
f.write(separator)
|
||||||
ok()
|
ok()
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
err(KeystoreOsError)
|
err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
finally:
|
finally:
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
@ -54,16 +56,17 @@ proc loadAppKeystore*(path: string,
|
||||||
|
|
||||||
# If no keystore exists at path we create a new empty one with passed keystore parameters
|
# If no keystore exists at path we create a new empty one with passed keystore parameters
|
||||||
if fileExists(path) == false:
|
if fileExists(path) == false:
|
||||||
let newKeystore = createAppKeystore(path, appInfo, separator)
|
let newKeystoreRes = createAppKeystore(path, appInfo, separator)
|
||||||
if newKeystore.isErr():
|
if newKeystoreRes.isErr():
|
||||||
return err(KeystoreCreateKeystoreError)
|
return err(newKeystoreRes.error)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# We read all the file contents
|
# We read all the file contents
|
||||||
var f: File
|
var f: File
|
||||||
if not f.open(path, fmRead):
|
if not f.open(path, fmRead):
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: "Cannot open file for reading"))
|
||||||
let fileContents = readAll(f)
|
let fileContents = readAll(f)
|
||||||
|
|
||||||
# We iterate over each substring split by separator (which we expect to correspond to a single keystore json)
|
# We iterate over each substring split by separator (which we expect to correspond to a single keystore json)
|
||||||
|
@ -92,23 +95,28 @@ proc loadAppKeystore*(path: string,
|
||||||
break
|
break
|
||||||
# TODO: we might continue rather than return for some of these errors
|
# TODO: we might continue rather than return for some of these errors
|
||||||
except JsonParsingError:
|
except JsonParsingError:
|
||||||
return err(KeystoreJsonError)
|
return err(AppKeystoreError(kind: KeystoreJsonError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return err(KeystoreJsonError)
|
return err(AppKeystoreError(kind: KeystoreJsonError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
except OSError:
|
except OSError:
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
except Exception: #parseJson raises Exception
|
except Exception: #parseJson raises Exception
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
return err(KeystoreIoError)
|
return err(AppKeystoreError(kind: KeystoreIoError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
|
|
||||||
return ok(matchingAppKeystore)
|
return ok(matchingAppKeystore)
|
||||||
|
|
||||||
|
|
||||||
# Adds a sequence of membership credential to the keystore matching the application, appIdentifier and version filters.
|
# Adds a membership credential to the keystore matching the application, appIdentifier and version filters.
|
||||||
proc addMembershipCredentials*(path: string,
|
proc addMembershipCredentials*(path: string,
|
||||||
credentials: seq[MembershipCredentials],
|
membership: KeystoreMembership,
|
||||||
password: string,
|
password: string,
|
||||||
appInfo: AppInfo,
|
appInfo: AppInfo,
|
||||||
separator: string = "\n"): KeystoreResult[void] =
|
separator: string = "\n"): KeystoreResult[void] =
|
||||||
|
@ -118,77 +126,38 @@ proc addMembershipCredentials*(path: string,
|
||||||
let jsonKeystoreRes = loadAppKeystore(path, appInfo, separator)
|
let jsonKeystoreRes = loadAppKeystore(path, appInfo, separator)
|
||||||
|
|
||||||
if jsonKeystoreRes.isErr():
|
if jsonKeystoreRes.isErr():
|
||||||
return err(KeystoreLoadKeystoreError)
|
return err(jsonKeystoreRes.error)
|
||||||
|
|
||||||
# We load the JSON node corresponding to the app keystore
|
# We load the JSON node corresponding to the app keystore
|
||||||
var jsonKeystore = jsonKeystoreRes.get()
|
var jsonKeystore = jsonKeystoreRes.get()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if jsonKeystore.hasKey("credentials"):
|
if jsonKeystore.hasKey("credentials"):
|
||||||
|
|
||||||
# We get all credentials in keystore
|
# We get all credentials in keystore
|
||||||
var keystoreCredentials = jsonKeystore["credentials"]
|
let keystoreCredentials = jsonKeystore["credentials"]
|
||||||
var found: bool
|
let key = membership.hash()
|
||||||
|
if keystoreCredentials.hasKey(key):
|
||||||
|
# noop
|
||||||
|
return ok()
|
||||||
|
|
||||||
for membershipCredential in credentials:
|
let encodedMembershipCredential = membership.encode()
|
||||||
|
let keyfileRes = createKeyFileJson(encodedMembershipCredential, password)
|
||||||
|
if keyfileRes.isErr():
|
||||||
|
return err(AppKeystoreError(kind: KeystoreCreateKeyfileError,
|
||||||
|
msg: $keyfileRes.error))
|
||||||
|
|
||||||
# A flag to tell us if the keystore contains a credential associated to the input identity credential, i.e. membershipCredential
|
# We add it to the credentials field of the keystore
|
||||||
found = false
|
jsonKeystore["credentials"][key] = keyfileRes.get()
|
||||||
|
|
||||||
for keystoreCredential in keystoreCredentials.mitems():
|
|
||||||
# keystoreCredential is encrypted. We decrypt it
|
|
||||||
let decodedKeyfileRes = decodeKeyFileJson(keystoreCredential, password)
|
|
||||||
if decodedKeyfileRes.isOk():
|
|
||||||
|
|
||||||
# we parse the json decrypted keystoreCredential
|
|
||||||
let decodedCredentialRes = decode(decodedKeyfileRes.get())
|
|
||||||
|
|
||||||
if decodedCredentialRes.isOk():
|
|
||||||
let keyfileMembershipCredential = decodedCredentialRes.get()
|
|
||||||
|
|
||||||
# We check if the decrypted credential has its identityCredential field equal to the input credential
|
|
||||||
if keyfileMembershipCredential.identityCredential == membershipCredential.identityCredential:
|
|
||||||
# idCredential is present in keystore. We add the input credential membership group to the one contained in the decrypted keystore credential (we deduplicate groups using sets)
|
|
||||||
var allMemberships = toSeq(toHashSet(keyfileMembershipCredential.membershipGroups) + toHashSet(membershipCredential.membershipGroups))
|
|
||||||
|
|
||||||
# We sort membership groups, otherwise we will not have deterministic results in tests
|
|
||||||
allMemberships.sort(sortMembershipGroup)
|
|
||||||
|
|
||||||
# we define the updated credential with the updated membership sets
|
|
||||||
let updatedCredential = MembershipCredentials(identityCredential: keyfileMembershipCredential.identityCredential, membershipGroups: allMemberships)
|
|
||||||
|
|
||||||
# we re-encrypt creating a new keyfile
|
|
||||||
let encodedUpdatedCredential = updatedCredential.encode()
|
|
||||||
let updatedCredentialKeyfileRes = createKeyFileJson(encodedUpdatedCredential, password)
|
|
||||||
if updatedCredentialKeyfileRes.isErr():
|
|
||||||
return err(KeystoreCreateKeyfileError)
|
|
||||||
|
|
||||||
# we update the original credential field in keystoreCredentials
|
|
||||||
keystoreCredential = updatedCredentialKeyfileRes.get()
|
|
||||||
|
|
||||||
found = true
|
|
||||||
|
|
||||||
# We stop decrypting other credentials in the keystore
|
|
||||||
break
|
|
||||||
|
|
||||||
# If no credential in keystore with same input identityCredential value is found, we add it
|
|
||||||
if found == false:
|
|
||||||
|
|
||||||
let encodedMembershipCredential = membershipCredential.encode()
|
|
||||||
let keyfileRes = createKeyFileJson(encodedMembershipCredential, password)
|
|
||||||
if keyfileRes.isErr():
|
|
||||||
return err(KeystoreCreateKeyfileError)
|
|
||||||
|
|
||||||
# We add it to the credentials field of the keystore
|
|
||||||
jsonKeystore["credentials"].add(keyfileRes.get())
|
|
||||||
|
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
return err(KeystoreJsonError)
|
return err(AppKeystoreError(kind: KeystoreJsonError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
|
|
||||||
# We save to disk the (updated) keystore.
|
# We save to disk the (updated) keystore.
|
||||||
if save(jsonKeystore, path, separator).isErr():
|
let saveRes = save(jsonKeystore, path, separator)
|
||||||
return err(KeystoreOsError)
|
if saveRes.isErr():
|
||||||
|
return err(saveRes.error)
|
||||||
|
|
||||||
return ok()
|
return ok()
|
||||||
|
|
||||||
|
@ -196,18 +165,15 @@ proc addMembershipCredentials*(path: string,
|
||||||
# identity credentials and membership contracts
|
# identity credentials and membership contracts
|
||||||
proc getMembershipCredentials*(path: string,
|
proc getMembershipCredentials*(path: string,
|
||||||
password: string,
|
password: string,
|
||||||
filterIdentityCredentials: seq[IdentityCredential] = @[],
|
query: KeystoreMembership,
|
||||||
filterMembershipContracts: seq[MembershipContract] = @[],
|
appInfo: AppInfo): KeystoreResult[KeystoreMembership] =
|
||||||
appInfo: AppInfo): KeystoreResult[seq[MembershipCredentials]] =
|
|
||||||
|
|
||||||
var outputMembershipCredentials: seq[MembershipCredentials] = @[]
|
|
||||||
|
|
||||||
# We load the keystore corresponding to the desired parameters
|
# We load the keystore corresponding to the desired parameters
|
||||||
# This call ensures that JSON has all required fields
|
# This call ensures that JSON has all required fields
|
||||||
let jsonKeystoreRes = loadAppKeystore(path, appInfo)
|
let jsonKeystoreRes = loadAppKeystore(path, appInfo)
|
||||||
|
|
||||||
if jsonKeystoreRes.isErr():
|
if jsonKeystoreRes.isErr():
|
||||||
return err(KeystoreLoadKeystoreError)
|
return err(jsonKeystoreRes.error)
|
||||||
|
|
||||||
# We load the JSON node corresponding to the app keystore
|
# We load the JSON node corresponding to the app keystore
|
||||||
var jsonKeystore = jsonKeystoreRes.get()
|
var jsonKeystore = jsonKeystoreRes.get()
|
||||||
|
@ -215,27 +181,24 @@ proc getMembershipCredentials*(path: string,
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if jsonKeystore.hasKey("credentials"):
|
if jsonKeystore.hasKey("credentials"):
|
||||||
|
|
||||||
# We get all credentials in keystore
|
# We get all credentials in keystore
|
||||||
var keystoreCredentials = jsonKeystore["credentials"]
|
var keystoreCredentials = jsonKeystore["credentials"]
|
||||||
|
let key = query.hash()
|
||||||
|
if not keystoreCredentials.hasKey(key):
|
||||||
|
# error
|
||||||
|
return err(AppKeystoreError(kind: KeystoreCredentialNotFoundError,
|
||||||
|
msg: "Credential not found in keystore"))
|
||||||
|
|
||||||
for keystoreCredential in keystoreCredentials.mitems():
|
let keystoreCredential = keystoreCredentials[key]
|
||||||
|
let decodedKeyfileRes = decodeKeyFileJson(keystoreCredential, password)
|
||||||
# keystoreCredential is encrypted. We decrypt it
|
if decodedKeyfileRes.isErr():
|
||||||
let decodedKeyfileRes = decodeKeyFileJson(keystoreCredential, password)
|
return err(AppKeystoreError(kind: KeystoreReadKeyfileError,
|
||||||
if decodedKeyfileRes.isOk():
|
msg: $decodedKeyfileRes.error))
|
||||||
# we parse the json decrypted keystoreCredential
|
# we parse the json decrypted keystoreCredential
|
||||||
let decodedCredentialRes = decode(decodedKeyfileRes.get())
|
let decodedCredentialRes = decode(decodedKeyfileRes.get())
|
||||||
|
let keyfileMembershipCredential = decodedCredentialRes.get()
|
||||||
if decodedCredentialRes.isOk():
|
return ok(keyfileMembershipCredential)
|
||||||
let keyfileMembershipCredential = decodedCredentialRes.get()
|
|
||||||
|
|
||||||
let filteredCredentialOpt = filterCredential(keyfileMembershipCredential, filterIdentityCredentials, filterMembershipContracts)
|
|
||||||
|
|
||||||
if filteredCredentialOpt.isSome():
|
|
||||||
outputMembershipCredentials.add(filteredCredentialOpt.get())
|
|
||||||
|
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
return err(KeystoreJsonError)
|
return err(AppKeystoreError(kind: KeystoreJsonError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
return ok(outputMembershipCredentials)
|
|
||||||
|
|
|
@ -4,8 +4,9 @@ else:
|
||||||
{.push raises: [].}
|
{.push raises: [].}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/sequtils,
|
std/[sequtils, tables],
|
||||||
stew/[results, endians2],
|
stew/[results, endians2],
|
||||||
|
nimcrypto,
|
||||||
stint
|
stint
|
||||||
|
|
||||||
# NOTE: 256-bytes long credentials are due to the use of BN254 in RLN. Other implementations/curves might have a different byte size
|
# NOTE: 256-bytes long credentials are due to the use of BN254 in RLN. Other implementations/curves might have a different byte size
|
||||||
|
@ -88,13 +89,28 @@ type MembershipContract* = object
|
||||||
chainId*: string
|
chainId*: string
|
||||||
address*: string
|
address*: string
|
||||||
|
|
||||||
type MembershipGroup* = object
|
type KeystoreMembership* = ref object of RootObj
|
||||||
membershipContract*: MembershipContract
|
membershipContract*: MembershipContract
|
||||||
treeIndex*: MembershipIndex
|
treeIndex*: MembershipIndex
|
||||||
|
identityCredential*: IdentityCredential
|
||||||
|
|
||||||
type MembershipCredentials* = object
|
proc `$`*(m: KeystoreMembership): string =
|
||||||
identityCredential*: IdentityCredential
|
return "KeystoreMembership(chainId: " & m.membershipContract.chainId & ", contractAddress: " & m.membershipContract.address & ", treeIndex: " & $m.treeIndex & ", identityCredential: " & $m.identityCredential & ")"
|
||||||
membershipGroups*: seq[MembershipGroup]
|
|
||||||
|
proc `==`*(x, y: KeystoreMembership): bool =
|
||||||
|
return x.membershipContract.chainId == y.membershipContract.chainId and
|
||||||
|
x.membershipContract.address == y.membershipContract.address and
|
||||||
|
x.treeIndex == y.treeIndex and
|
||||||
|
x.identityCredential.idTrapdoor == y.identityCredential.idTrapdoor and
|
||||||
|
x.identityCredential.idNullifier == y.identityCredential.idNullifier and
|
||||||
|
x.identityCredential.idSecretHash == y.identityCredential.idSecretHash and
|
||||||
|
x.identityCredential.idCommitment == y.identityCredential.idCommitment
|
||||||
|
|
||||||
|
proc hash*(m: KeystoreMembership): string =
|
||||||
|
# hash together the chainId, address and treeIndex
|
||||||
|
return $sha256.digest(m.membershipContract.chainId & m.membershipContract.address & $m.treeIndex)
|
||||||
|
|
||||||
|
type MembershipTable* = Table[string, KeystoreMembership]
|
||||||
|
|
||||||
type AppInfo* = object
|
type AppInfo* = object
|
||||||
application*: string
|
application*: string
|
||||||
|
@ -104,11 +120,11 @@ type AppInfo* = object
|
||||||
type AppKeystore* = object
|
type AppKeystore* = object
|
||||||
application*: string
|
application*: string
|
||||||
appIdentifier*: string
|
appIdentifier*: string
|
||||||
credentials*: seq[MembershipCredentials]
|
credentials*: MembershipTable
|
||||||
version*: string
|
version*: string
|
||||||
|
|
||||||
type
|
type
|
||||||
AppKeystoreError* = enum
|
AppKeystoreErrorKind* = enum
|
||||||
KeystoreOsError = "keystore error: OS specific error"
|
KeystoreOsError = "keystore error: OS specific error"
|
||||||
KeystoreIoError = "keystore error: IO specific error"
|
KeystoreIoError = "keystore error: IO specific error"
|
||||||
KeystoreJsonKeyError = "keystore error: fields not present in JSON"
|
KeystoreJsonKeyError = "keystore error: fields not present in JSON"
|
||||||
|
@ -119,5 +135,14 @@ type
|
||||||
KeystoreCreateKeyfileError = "Error while creating keyfile for credentials"
|
KeystoreCreateKeyfileError = "Error while creating keyfile for credentials"
|
||||||
KeystoreSaveKeyfileError = "Error while saving keyfile for credentials"
|
KeystoreSaveKeyfileError = "Error while saving keyfile for credentials"
|
||||||
KeystoreReadKeyfileError = "Error while reading keyfile for credentials"
|
KeystoreReadKeyfileError = "Error while reading keyfile for credentials"
|
||||||
|
KeystoreCredentialAlreadyPresentError = "Error while adding credentials to keystore: credential already present"
|
||||||
|
KeystoreCredentialNotFoundError = "Error while searching credentials in keystore: credential not found"
|
||||||
|
|
||||||
|
AppKeystoreError* = object
|
||||||
|
kind*: AppKeystoreErrorKind
|
||||||
|
msg*: string
|
||||||
|
|
||||||
|
proc `$`*(e: AppKeystoreError) : string =
|
||||||
|
return $e.kind & ": " & e.msg
|
||||||
|
|
||||||
type KeystoreResult*[T] = Result[T, AppKeystoreError]
|
type KeystoreResult*[T] = Result[T, AppKeystoreError]
|
|
@ -5,7 +5,9 @@ else:
|
||||||
|
|
||||||
import
|
import
|
||||||
json,
|
json,
|
||||||
std/[options, os, sequtils],
|
std/[os, sequtils]
|
||||||
|
|
||||||
|
import
|
||||||
./keyfile,
|
./keyfile,
|
||||||
./protocol_types
|
./protocol_types
|
||||||
|
|
||||||
|
@ -13,25 +15,22 @@ import
|
||||||
proc hasKeys*(data: JsonNode, keys: openArray[string]): bool =
|
proc hasKeys*(data: JsonNode, keys: openArray[string]): bool =
|
||||||
return all(keys, proc (key: string): bool = return data.hasKey(key))
|
return all(keys, proc (key: string): bool = return data.hasKey(key))
|
||||||
|
|
||||||
# Defines how to sort membership groups
|
|
||||||
proc sortMembershipGroup*(a,b: MembershipGroup): int =
|
|
||||||
return cmp(a.membershipContract.address, b.membershipContract.address)
|
|
||||||
|
|
||||||
# Safely saves a Keystore's JsonNode to disk.
|
# Safely saves a Keystore's JsonNode to disk.
|
||||||
# If exists, the destination file is renamed with extension .bkp; the file is written at its destination and the .bkp file is removed if write is successful, otherwise is restored
|
# If exists, the destination file is renamed with extension .bkp; the file is written at its destination and the .bkp file is removed if write is successful, otherwise is restored
|
||||||
proc save*(json: JsonNode, path: string, separator: string): KeystoreResult[void] =
|
proc save*(json: JsonNode, path: string, separator: string): KeystoreResult[void] =
|
||||||
|
|
||||||
# We first backup the current keystore
|
# We first backup the current keystore
|
||||||
if fileExists(path):
|
if fileExists(path):
|
||||||
try:
|
try:
|
||||||
moveFile(path, path & ".bkp")
|
moveFile(path, path & ".bkp")
|
||||||
except: # TODO: Fix "BareExcept" warning
|
except: # TODO: Fix "BareExcept" warning
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: "could not backup keystore: " & getCurrentExceptionMsg()))
|
||||||
|
|
||||||
# We save the updated json
|
# We save the updated json
|
||||||
var f: File
|
var f: File
|
||||||
if not f.open(path, fmAppend):
|
if not f.open(path, fmAppend):
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: getCurrentExceptionMsg()))
|
||||||
try:
|
try:
|
||||||
# To avoid other users/attackers to be able to read keyfiles, we make the file readable/writable only by the running user
|
# To avoid other users/attackers to be able to read keyfiles, we make the file readable/writable only by the running user
|
||||||
setFilePermissions(path, {fpUserWrite, fpUserRead})
|
setFilePermissions(path, {fpUserWrite, fpUserRead})
|
||||||
|
@ -47,8 +46,10 @@ proc save*(json: JsonNode, path: string, separator: string): KeystoreResult[void
|
||||||
moveFile(path & ".bkp", path)
|
moveFile(path & ".bkp", path)
|
||||||
except: # TODO: Fix "BareExcept" warning
|
except: # TODO: Fix "BareExcept" warning
|
||||||
# Unlucky, we just fail
|
# Unlucky, we just fail
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
return err(KeystoreOsError)
|
msg: "could not restore keystore backup: " & getCurrentExceptionMsg()))
|
||||||
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: "could not write keystore: " & getCurrentExceptionMsg()))
|
||||||
finally:
|
finally:
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
@ -57,39 +58,7 @@ proc save*(json: JsonNode, path: string, separator: string): KeystoreResult[void
|
||||||
try:
|
try:
|
||||||
removeFile(path & ".bkp")
|
removeFile(path & ".bkp")
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
return err(KeystoreOsError)
|
return err(AppKeystoreError(kind: KeystoreOsError,
|
||||||
|
msg: "could not remove keystore backup: " & getCurrentExceptionMsg()))
|
||||||
|
|
||||||
return ok()
|
return ok()
|
||||||
|
|
||||||
# Filters a membership credential based on either input identity credential's value, membership contracts or both
|
|
||||||
proc filterCredential*(credential: MembershipCredentials,
|
|
||||||
filterIdentityCredentials: seq[IdentityCredential],
|
|
||||||
filterMembershipContracts: seq[MembershipContract]): Option[MembershipCredentials] =
|
|
||||||
|
|
||||||
# We filter by identity credentials
|
|
||||||
if filterIdentityCredentials.len() != 0:
|
|
||||||
if (credential.identityCredential in filterIdentityCredentials) == false:
|
|
||||||
return none(MembershipCredentials)
|
|
||||||
|
|
||||||
# We filter by membership groups credentials
|
|
||||||
if filterMembershipContracts.len() != 0:
|
|
||||||
# Here we keep only groups that match a contract in the filter
|
|
||||||
var membershipGroupsIntersection: seq[MembershipGroup] = @[]
|
|
||||||
# We check if we have a group in the input credential matching any contract in the filter
|
|
||||||
for membershipGroup in credential.membershipGroups:
|
|
||||||
if membershipGroup.membershipContract in filterMembershipContracts:
|
|
||||||
membershipGroupsIntersection.add(membershipGroup)
|
|
||||||
|
|
||||||
if membershipGroupsIntersection.len() != 0:
|
|
||||||
# If we have a match on some groups, we return the credential with filtered groups
|
|
||||||
return some(MembershipCredentials(identityCredential: credential.identityCredential,
|
|
||||||
membershipGroups: membershipGroupsIntersection))
|
|
||||||
|
|
||||||
else:
|
|
||||||
return none(MembershipCredentials)
|
|
||||||
|
|
||||||
# We hit this return only if
|
|
||||||
# - filterIdentityCredentials.len() == 0 and filterMembershipContracts.len() == 0 (no filter)
|
|
||||||
# - filterIdentityCredentials.len() != 0 and filterMembershipContracts.len() == 0 (filter only on identity credential)
|
|
||||||
# Indeed, filterMembershipContracts.len() != 0 will have its exclusive return based on all values of membershipGroupsIntersection.len()
|
|
||||||
return some(credential)
|
|
||||||
|
|
|
@ -53,4 +53,4 @@ const MaxEpochGap* = uint64(MaxClockGapSeconds/EpochUnitSeconds)
|
||||||
|
|
||||||
# RLN Keystore defaults
|
# RLN Keystore defaults
|
||||||
const
|
const
|
||||||
RLNAppInfo* = AppInfo(application: "waku-rln-relay", appIdentifier: "01234567890abcdef", version: "0.1")
|
RLNAppInfo* = AppInfo(application: "waku-rln-relay", appIdentifier: "01234567890abcdef", version: "0.2")
|
||||||
|
|
|
@ -58,8 +58,6 @@ type
|
||||||
registrationTxHash*: Option[TxHash]
|
registrationTxHash*: Option[TxHash]
|
||||||
chainId*: Option[Quantity]
|
chainId*: Option[Quantity]
|
||||||
keystorePath*: Option[string]
|
keystorePath*: Option[string]
|
||||||
keystoreIndex*: uint
|
|
||||||
membershipGroupIndex*: uint
|
|
||||||
keystorePassword*: Option[string]
|
keystorePassword*: Option[string]
|
||||||
registrationHandler*: Option[RegistrationHandler]
|
registrationHandler*: Option[RegistrationHandler]
|
||||||
# this buffer exists to backfill appropriate roots for the merkle tree,
|
# this buffer exists to backfill appropriate roots for the merkle tree,
|
||||||
|
@ -433,19 +431,24 @@ method init*(g: OnchainGroupManager): Future[void] {.async.} =
|
||||||
g.registryContract = some(registryContract)
|
g.registryContract = some(registryContract)
|
||||||
|
|
||||||
if g.keystorePath.isSome() and g.keystorePassword.isSome():
|
if g.keystorePath.isSome() and g.keystorePassword.isSome():
|
||||||
|
if g.membershipIndex.isNone():
|
||||||
|
raise newException(CatchableError, "membership index is not set when keystore is provided")
|
||||||
|
let keystoreQuery = KeystoreMembership(
|
||||||
|
membershipContract: MembershipContract(
|
||||||
|
chainId: $g.chainId.get(),
|
||||||
|
address: g.ethContractAddress
|
||||||
|
),
|
||||||
|
treeIndex: MembershipIndex(g.membershipIndex.get()),
|
||||||
|
)
|
||||||
waku_rln_membership_credentials_import_duration_seconds.nanosecondTime:
|
waku_rln_membership_credentials_import_duration_seconds.nanosecondTime:
|
||||||
let parsedCredsRes = getMembershipCredentials(path = g.keystorePath.get(),
|
let keystoreCredRes = getMembershipCredentials(path = g.keystorePath.get(),
|
||||||
password = g.keystorePassword.get(),
|
password = g.keystorePassword.get(),
|
||||||
filterMembershipContracts = @[MembershipContract(chainId: $chainId,
|
query = keystoreQuery,
|
||||||
address: g.ethContractAddress)],
|
appInfo = RLNAppInfo)
|
||||||
appInfo = RLNAppInfo)
|
if keystoreCredRes.isErr():
|
||||||
if parsedCredsRes.isErr():
|
raise newException(ValueError, "could not parse the keystore: " & $keystoreCredRes.error)
|
||||||
raise newException(ValueError, "could not parse the keystore: " & $parsedCredsRes.error())
|
let keystoreCred = keystoreCredRes.get()
|
||||||
let parsedCreds = parsedCredsRes.get()
|
g.idCredentials = some(keystoreCred.identityCredential)
|
||||||
if parsedCreds.len == 0:
|
|
||||||
raise newException(ValueError, "keystore is empty")
|
|
||||||
g.idCredentials = some(parsedCreds[g.keystoreIndex].identityCredential)
|
|
||||||
g.membershipIndex = some(parsedCreds[g.keystoreIndex].membershipGroups[g.membershipGroupIndex].treeIndex)
|
|
||||||
|
|
||||||
let metadataGetRes = g.rlnInstance.getMetadata()
|
let metadataGetRes = g.rlnInstance.getMetadata()
|
||||||
if metadataGetRes.isErr():
|
if metadataGetRes.isErr():
|
||||||
|
|
|
@ -32,11 +32,10 @@ logScope:
|
||||||
type WakuRlnConfig* = object
|
type WakuRlnConfig* = object
|
||||||
rlnRelayDynamic*: bool
|
rlnRelayDynamic*: bool
|
||||||
rlnRelayCredIndex*: uint
|
rlnRelayCredIndex*: uint
|
||||||
rlnRelayMembershipGroupIndex*: uint
|
|
||||||
rlnRelayEthContractAddress*: string
|
rlnRelayEthContractAddress*: string
|
||||||
rlnRelayEthClientAddress*: string
|
rlnRelayEthClientAddress*: string
|
||||||
rlnRelayCredPath*: string
|
rlnRelayCredPath*: string
|
||||||
rlnRelayCredentialsPassword*: string
|
rlnRelayCredPassword*: string
|
||||||
rlnRelayTreePath*: string
|
rlnRelayTreePath*: string
|
||||||
rlnRelayBandwidthThreshold*: int
|
rlnRelayBandwidthThreshold*: int
|
||||||
|
|
||||||
|
@ -343,7 +342,6 @@ proc mount(conf: WakuRlnConfig,
|
||||||
): Future[WakuRlnRelay] {.async.} =
|
): Future[WakuRlnRelay] {.async.} =
|
||||||
var
|
var
|
||||||
groupManager: GroupManager
|
groupManager: GroupManager
|
||||||
credentials: MembershipCredentials
|
|
||||||
# create an RLN instance
|
# create an RLN instance
|
||||||
let rlnInstanceRes = createRLNInstance(tree_path = conf.rlnRelayTreePath)
|
let rlnInstanceRes = createRLNInstance(tree_path = conf.rlnRelayTreePath)
|
||||||
if rlnInstanceRes.isErr():
|
if rlnInstanceRes.isErr():
|
||||||
|
@ -365,15 +363,14 @@ proc mount(conf: WakuRlnConfig,
|
||||||
if s == "": none(string) else: some(s)
|
if s == "": none(string) else: some(s)
|
||||||
let
|
let
|
||||||
rlnRelayCredPath = useValueOrNone(conf.rlnRelayCredPath)
|
rlnRelayCredPath = useValueOrNone(conf.rlnRelayCredPath)
|
||||||
rlnRelayCredentialsPassword = useValueOrNone(conf.rlnRelayCredentialsPassword)
|
rlnRelayCredPassword = useValueOrNone(conf.rlnRelayCredPassword)
|
||||||
groupManager = OnchainGroupManager(ethClientUrl: conf.rlnRelayEthClientAddress,
|
groupManager = OnchainGroupManager(ethClientUrl: conf.rlnRelayEthClientAddress,
|
||||||
ethContractAddress: $conf.rlnRelayEthContractAddress,
|
ethContractAddress: $conf.rlnRelayEthContractAddress,
|
||||||
rlnInstance: rlnInstance,
|
rlnInstance: rlnInstance,
|
||||||
registrationHandler: registrationHandler,
|
registrationHandler: registrationHandler,
|
||||||
keystorePath: rlnRelayCredPath,
|
keystorePath: rlnRelayCredPath,
|
||||||
keystorePassword: rlnRelayCredentialsPassword,
|
keystorePassword: rlnRelayCredPassword,
|
||||||
keystoreIndex: conf.rlnRelayCredIndex,
|
membershipIndex: some(conf.rlnRelayCredIndex))
|
||||||
membershipGroupIndex: conf.rlnRelayMembershipGroupIndex)
|
|
||||||
# Initialize the groupManager
|
# Initialize the groupManager
|
||||||
await groupManager.init()
|
await groupManager.init()
|
||||||
# Start the group sync
|
# Start the group sync
|
||||||
|
|
Loading…
Reference in New Issue