Skip to content

Commit

Permalink
Initial public version of the Verifying Web3Signer functionality (#4912)
Browse files Browse the repository at this point in the history
* Allow the list of proved properties for web3signer to be configured
* Document the Web3Signer setups (regular, distributed and verified)
  • Loading branch information
zah authored May 9, 2023
1 parent 3a1c468 commit 5bf9284
Show file tree
Hide file tree
Showing 13 changed files with 640 additions and 267 deletions.
30 changes: 16 additions & 14 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,16 @@ OK: 4/4 Fail: 0/4 Skip: 0/4
+ Voluntary exit signatures OK
```
OK: 8/8 Fail: 0/8 Skip: 0/8
## Nimbus remote signer/signing test (verifying-web3signer)
```diff
+ Signing BeaconBlock (getBlockSignature(altair)) OK
+ Signing BeaconBlock (getBlockSignature(bellatrix)) OK
+ Signing BeaconBlock (getBlockSignature(capella)) OK
+ Signing BeaconBlock (getBlockSignature(deneb)) OK
+ Signing BeaconBlock (getBlockSignature(phase0)) OK
+ Waiting for signing node (/upcheck) test OK
```
OK: 6/6 Fail: 0/6 Skip: 0/6
## Nimbus remote signer/signing test (web3signer)
```diff
+ Connection timeout test OK
Expand All @@ -382,16 +392,6 @@ OK: 8/8 Fail: 0/8 Skip: 0/8
+ Waiting for signing node (/upcheck) test OK
```
OK: 22/22 Fail: 0/22 Skip: 0/22
## Nimbus remote signer/signing test (web3signer-diva)
```diff
+ Signing BeaconBlock (getBlockSignature(altair)) OK
+ Signing BeaconBlock (getBlockSignature(bellatrix)) OK
+ Signing BeaconBlock (getBlockSignature(capella)) OK
+ Signing BeaconBlock (getBlockSignature(deneb)) OK
+ Signing BeaconBlock (getBlockSignature(phase0)) OK
+ Waiting for signing node (/upcheck) test OK
```
OK: 6/6 Fail: 0/6 Skip: 0/6
## Old database versions [Preset: mainnet]
```diff
+ pre-1.1.0 OK
Expand Down Expand Up @@ -420,11 +420,13 @@ OK: 12/12 Fail: 0/12 Skip: 0/12
OK: 1/1 Fail: 0/1 Skip: 0/1
## Remove keystore testing suite
```diff
+ Many remotes OK
+ Single remote OK
+ Verifying Signer / Many remotes OK
+ Verifying Signer / Single remote OK
+ vesion 1 OK
+ vesion 2 many remotes OK
+ vesion 2 single remote OK
```
OK: 3/3 Fail: 0/3 Skip: 0/3
OK: 5/5 Fail: 0/5 Skip: 0/5
## Serialization/deserialization [Beacon Node] [Preset: mainnet]
```diff
+ Deserialization test vectors OK
Expand Down Expand Up @@ -667,4 +669,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
OK: 9/9 Fail: 0/9 Skip: 0/9

---TOTAL---
OK: 380/385 Fail: 0/385 Skip: 5/385
OK: 382/387 Fail: 0/387 Skip: 5/387
7 changes: 4 additions & 3 deletions beacon_chain/nimbus_signing_node.nim
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# nimbus_sign_node
# Copyright (c) 2018-2022 Status Research & Development GmbH
# nimbus_signing_node
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import std/[tables, os, strutils]
import serialization, json_serialization,
json_serialization/std/[options, net],
Expand Down Expand Up @@ -256,7 +257,7 @@ proc installApiHandlers*(node: SigningNodeRef) =
let feeRecipientRoot = hash_tree_root(distinctBase(
node.config.expectedFeeRecipient.get()))

if not(is_valid_merkle_branch(feeRecipientRoot, proof.merkleProofs,
if not(is_valid_merkle_branch(feeRecipientRoot, proof.proof,
log2trunc(proof.index),
get_subtree_index(proof.index),
blockHeader.body_root)):
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/spec/eth2_apis/rest_types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ type

Web3SignerMerkleProof* = object
index*: GeneralizedIndex
merkleProofs* {.serializedFieldName: "merkle_proofs".}: seq[Eth2Digest]
proof*: seq[Eth2Digest]

Web3SignerRequestKind* {.pure.} = enum
AggregationSlot, AggregateAndProof, Attestation, Block, BlockV2,
Expand Down
203 changes: 149 additions & 54 deletions beacon_chain/spec/keystore.nim
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ type
ioHandle*: IoLockHandle
opened*: bool

RemoteSignerType* {.pure.} = enum
Web3Signer, VerifyingWeb3Signer

ProvenProperty* = object
path*: string
description*: Option[string]
phase0Index*: Option[GeneralizedIndex]
altairIndex*: Option[GeneralizedIndex]
bellatrixIndex*: Option[GeneralizedIndex]
capellaIndex*: Option[GeneralizedIndex]
denebIndex*: Option[GeneralizedIndex]

KeystoreData* = object
version*: uint64
pubkey*: ValidatorPubKey
Expand All @@ -157,7 +169,11 @@ type
flags*: set[RemoteKeystoreFlag]
remotes*: seq[RemoteSignerInfo]
threshold*: uint32
remoteType*: RemoteSignerType
case remoteType*: RemoteSignerType
of RemoteSignerType.Web3Signer:
discard
of RemoteSignerType.VerifyingWeb3Signer:
provenBlockProperties*: seq[ProvenProperty]

NetKeystore* = object
crypto*: Crypto
Expand All @@ -166,13 +182,14 @@ type
uuid*: string
version*: int

RemoteSignerType* {.pure.} = enum
Web3Signer, Web3SignerDiva

RemoteKeystore* = object
version*: uint64
description*: Option[string]
remoteType*: RemoteSignerType
case remoteType*: RemoteSignerType
of RemoteSignerType.Web3Signer:
discard
of RemoteSignerType.VerifyingWeb3Signer:
provenBlockProperties*: seq[ProvenProperty]
pubkey*: ValidatorPubKey
flags*: set[RemoteKeystoreFlag]
remotes*: seq[RemoteSignerInfo]
Expand Down Expand Up @@ -599,8 +616,9 @@ proc writeValue*(writer: var JsonWriter, value: RemoteKeystore)
case value.remoteType
of RemoteSignerType.Web3Signer:
writer.writeField("type", "web3signer")
of RemoteSignerType.Web3SignerDiva:
writer.writeField("type", "web3signer-diva")
of RemoteSignerType.VerifyingWeb3Signer:
writer.writeField("type", "verifying-web3signer")
writer.writeField("proven_block_properties", value.provenBlockProperties)
if value.description.isSome():
writer.writeField("description", value.description.get())
if RemoteKeystoreFlag.IgnoreSSLVerification in value.flags:
Expand All @@ -618,77 +636,139 @@ proc readValue*(reader: var JsonReader, value: var RemoteKeystore)
description: Option[string]
remote: Option[HttpHostUri]
remotes: Option[seq[RemoteSignerInfo]]
remoteType: Option[string]
remoteType: Option[RemoteSignerType]
provenBlockProperties: Option[seq[ProvenProperty]]
ignoreSslVerification: Option[bool]
pubkey: Option[ValidatorPubKey]
threshold: Option[uint32]
implicitVersion1 = false

# TODO: implementing deserializers for versioned objects
# manually is extremely error-prone. This should use
# the auto-generated deserializer from nim-json-serialization
for fieldName in readObjectFields(reader):
case fieldName:
of "pubkey":
if pubkey.isSome():
if pubkey.isSome:
reader.raiseUnexpectedField("Multiple `pubkey` fields found",
"RemoteKeystore")
pubkey = some(reader.readValue(ValidatorPubKey))
of "remote":
if version.isSome and version.get > 1:
reader.raiseUnexpectedField(
"The `remote` field is valid only in version 1 of the remote keystore format",
"RemoteKeystore")

if remote.isSome():
if remote.isSome:
reader.raiseUnexpectedField("Multiple `remote` fields found",
"RemoteKeystore")
if remotes.isSome:
reader.raiseUnexpectedField("The `remote` field cannot be specified together with `remotes`",
"RemoteKeystore")
remote = some(reader.readValue(HttpHostUri))
implicitVersion1 = true
of "remotes":
if remotes.isSome():
if remotes.isSome:
reader.raiseUnexpectedField("Multiple `remote` fields found",
"RemoteKeystore")
if remote.isSome:
reader.raiseUnexpectedField("The `remotes` field cannot be specified together with `remote`",
"RemoteKeystore")
if version.isNone:
reader.raiseUnexpectedField(
"The `remotes` field should be specified after the `version` field of the keystore",
"RemoteKeystore")
if version.get < 2:
reader.raiseUnexpectedField(
"The `remotes` field is valid only past version 2 of the remote keystore format",
"RemoteKeystore")
remotes = some(reader.readValue(seq[RemoteSignerInfo]))
of "version":
if version.isSome():
if version.isSome:
reader.raiseUnexpectedField("Multiple `version` fields found",
"RemoteKeystore")
version = some(reader.readValue(uint64))
if implicitVersion1 and version.get > 1'u64:
reader.raiseUnexpectedValue(
"Remote keystore format doesn't match the specified version number")
if version.get > 2'u64:
if version.get > 3'u64:
reader.raiseUnexpectedValue(
"Remote keystore version " & $version.get &
" requires a more recent version of Nimbus")
of "description":
let res = reader.readValue(string)
if description.isSome():
description = some(description.get() & "\n" & res)
else:
description = some(res)
if description.isSome:
reader.raiseUnexpectedField("Multiple `description` fields found",
"RemoteKeystore")
description = some(reader.readValue(string))
of "ignore_ssl_verification":
if ignoreSslVerification.isSome():
if ignoreSslVerification.isSome:
reader.raiseUnexpectedField("Multiple conflicting options found",
"RemoteKeystore")
ignoreSslVerification = some(reader.readValue(bool))
of "type":
if remoteType.isSome():
if remoteType.isSome:
reader.raiseUnexpectedField("Multiple `type` fields found",
"RemoteKeystore")
remoteType = some(reader.readValue(string))
if version.isNone:
reader.raiseUnexpectedField(
"The `type` field should be specified after the `version` field of the keystore",
"RemoteKeystore")
if version.get < 2:
reader.raiseUnexpectedField(
"The `type` field is valid only past version 2 of the remote keystore format",
"RemoteKeystore")
let remoteTypeValue = case reader.readValue(string).toLowerAscii()
of "web3signer":
RemoteSignerType.Web3Signer
of "verifying-web3signer":
RemoteSignerType.VerifyingWeb3Signer
else:
reader.raiseUnexpectedValue("Unsupported remote signer `type` value")
remoteType = some remoteTypeValue
of "proven_block_properties":
if provenBlockProperties.isSome:
reader.raiseUnexpectedField("Multiple `proven_block_properties` fields found",
"RemoteKeystore")
if version.isNone:
reader.raiseUnexpectedField(
"The `proven_block_properties` field should be specified after the `version` field of the keystore",
"RemoteKeystore")
if version.get < 3:
reader.raiseUnexpectedField(
"The `proven_block_properties` field is valid only past version 3 of the remote keystore format",
"RemoteKeystore")
if remoteType.isNone:
reader.raiseUnexpectedField(
"The `proven_block_properties` field should be specified after the `type` field of the keystore",
"RemoteKeystore")
if remoteType.get != RemoteSignerType.VerifyingWeb3Signer:
reader.raiseUnexpectedField(
"The `proven_block_properties` field can be specified only when the remote signer type is 'verifying-web3signer'",
"RemoteKeystore")
var provenProperties = reader.readValue(seq[ProvenProperty])
for prop in provenProperties.mitems:
if prop.path == ".execution_payload.fee_recipient":
prop.bellatrixIndex = some GeneralizedIndex(401)
prop.capellaIndex = some GeneralizedIndex(401)
prop.denebIndex = some GeneralizedIndex(401)
elif prop.path == ".graffiti":
prop.bellatrixIndex = some GeneralizedIndex(18)
prop.capellaIndex = some GeneralizedIndex(18)
prop.denebIndex = some GeneralizedIndex(18)
else:
reader.raiseUnexpectedValue("Keystores with proven properties different than " &
"`.execution_payload.fee_recipient` and `.graffiti` " &
"require a more recent version of Nimbus")
provenBlockProperties = some provenProperties
of "threshold":
if threshold.isSome():
if threshold.isSome:
reader.raiseUnexpectedField("Multiple `threshold` fields found",
"RemoteKeystore")
if version.isNone:
reader.raiseUnexpectedField(
"The `threshold` field should be specified after the `version` field of the keystore",
"RemoteKeystore")
if version.get < 2:
reader.raiseUnexpectedField(
"The `threshold` field is valid only past version 2 of the remote keystore format",
"RemoteKeystore")
threshold = some(reader.readValue(uint32))
else:
# Ignore unknown field names.
discard

if version.isNone():
reader.raiseUnexpectedValue("Field `version` is missing")
reader.raiseUnexpectedValue("The required field `version` is missing")
if remotes.isNone():
if remote.isSome and pubkey.isSome:
remotes = some @[RemoteSignerInfo(
Expand All @@ -697,22 +777,27 @@ proc readValue*(reader: var JsonReader, value: var RemoteKeystore)
url: remote.get
)]
else:
reader.raiseUnexpectedValue("Field `remotes` is missing")
reader.raiseUnexpectedValue("The required field `remotes` is missing")

if threshold.isNone:
if remotes.get.len > 1:
reader.raiseUnexpectedValue("The `threshold` field must be specified when using distributed keystores")
else:
if threshold.get.uint64 > remotes.get.lenu64:
reader.raiseUnexpectedValue("The specified `threshold` must be lower than the number of remote signers")

if pubkey.isNone():
reader.raiseUnexpectedValue("Field `pubkey` is missing")

let keystoreType =
if remoteType.isSome():
let res = remoteType.get()
case res.toLowerAscii()
of "web3signer":
RemoteSignerType.Web3Signer
of "web3signer-diva":
RemoteSignerType.Web3SignerDiva
else:
reader.raiseUnexpectedValue("Unsupported remote signer `type` value")
else:
RemoteSignerType.Web3Signer
if version.get >= 3:
if remoteType.isNone:
reader.raiseUnexpectedValue("The required field `type` is missing")
case remoteType.get
of RemoteSignerType.Web3Signer:
discard
of RemoteSignerType.VerifyingWeb3Signer:
if provenBlockProperties.isNone:
reader.raiseUnexpectedValue("The required field `proven_block_properties` is missing")

let keystoreFlags =
block:
Expand All @@ -721,14 +806,24 @@ proc readValue*(reader: var JsonReader, value: var RemoteKeystore)
res.incl(RemoteKeystoreFlag.IgnoreSSLVerification)
res

value = RemoteKeystore(
version: 2'u64,
pubkey: pubkey.get,
description: description,
remoteType: keystoreType,
remotes: remotes.get,
threshold: threshold.get(1),
)
value = case remoteType.get(RemoteSignerType.Web3Signer)
of RemoteSignerType.Web3Signer:
RemoteKeystore(
version: 2'u64,
pubkey: pubkey.get,
description: description,
remoteType: RemoteSignerType.Web3Signer,
remotes: remotes.get,
threshold: threshold.get(1))
of RemoteSignerType.VerifyingWeb3Signer:
RemoteKeystore(
version: 2'u64,
pubkey: pubkey.get,
description: description,
remoteType: RemoteSignerType.VerifyingWeb3Signer,
provenBlockProperties: provenBlockProperties.get,
remotes: remotes.get,
threshold: threshold.get(1))

template writeValue*(w: var JsonWriter,
value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv) =
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/spec/mev/bellatrix_mev.nim
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type
signature*: ValidatorSig

# https://github.com/ethereum/builder-specs/blob/v0.3.0/specs/bellatrix/builder.md#blindedbeaconblockbody
BlindedBeaconBlockBody = object
BlindedBeaconBlockBody* = object
randao_reveal*: ValidatorSig
eth1_data*: Eth1Data
graffiti*: GraffitiBytes
Expand Down
Loading

0 comments on commit 5bf9284

Please sign in to comment.