Skip to content

Commit edeaa3f

Browse files
committed
Merge branch 'main' into release-builds
2 parents 72f3924 + 53e617d commit edeaa3f

File tree

18 files changed

+106
-114
lines changed

18 files changed

+106
-114
lines changed

LICENSE

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
BSD 3-Clause License
2+
3+
Copyright (C) 2019-2024, Ava Labs, Inc.
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are met:
8+
9+
1. Redistributions of source code must retain the above copyright notice, this
10+
list of conditions and the following disclaimer.
11+
12+
2. Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
3. Neither the name of the copyright holder nor the names of its
17+
contributors may be used to endorse or promote products derived from
18+
this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 26 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,76 @@
1-
# Cubist Signer
1+
# Cube Signer Sidecar
22

3-
A proxy to be run alongside an
4-
[`avalanchego`](https://github.com/ava-labs/avalanchego) validator, used for all
5-
bls signatures (currently used for peer handshakes and ICM message signatures).
6-
This service runs a [gRPC](https://grpc.io/) server, implementing the
7-
[`signer.proto` service definition](https://github.com/ava-labs/avalanchego/blob/master/proto/signer/signer.proto).
8-
When the `gRPC` endpoints are hit, they make subsequent requests to the
9-
[Cubist-API](https://signer-docs.cubist.dev/api)
3+
This repository contains a proxy that can be run alongside [AvalancheGo](https://github.com/ava-labs/avalanchego) nodes to integrate them with [CubeSigner](https://cubist.dev/products/cubesigner-self-custody) for secure BLS key management powered by [Cubist](https://cubist.dev/). AvalancheGo nodes currently use BLS keys for peer handshakes and to sign [ICM messages](https://build.avax.network/docs/cross-chain/avalanche-warp-messaging/overview). By integrating with CubeSigner, AvalancheGo nodes do not need to store their BLS keys in memory or on disk. Instead, the keys are generated and kept within a remote hardware security module (HSM), and the AvalancheGo node is configured to request signatures from that HSM as needed via this `cube-signer-sidecar`.
4+
5+
This service runs a [gRPC](https://grpc.io/) server, implementing the [`signer.proto` service definition](https://github.com/ava-labs/avalanchego/blob/master/proto/signer/signer.proto). When the `gRPC` endpoints are hit, they make subsequent requests to the [CubeSigner API](https://signer-docs.cubist.dev/api) to get the requested signature.
6+
7+
A Cubist account is required to be able to properly use the `cube-signer-sidecar`.
108

119
## Building
1210

13-
The `api/` directory contains generated code from the
14-
[Cubist OpenAPI specification](https://raw.githubusercontent.com/cubist-labs/CubeSigner-TypeScript-SDK/main/packages/sdk/spec/openapi.json).
15-
We use the [`spec/get-schemas.go`] script to filter the API-spec for the three
16-
endpoints that we care about as well as all the schemas that those endpoints
17-
use. The filtered Open-API specification is output to
18-
`spec/filtered-openapi.json`.
11+
The `api/` directory contains generated code from the [CubeSigner OpenAPI specification](https://raw.githubusercontent.com/cubist-labs/CubeSigner-TypeScript-SDK/main/packages/sdk/spec/openapi.json). The [`spec/get-schemas.go`] script is used to filter the API-spec for the three relevant endpoints, as well as all the schemas that those endpoints
12+
use. The filtered Open-API specification is output to `spec/filtered-openapi.json`.
1913

20-
If there are changes in `spec/filtered-openapi.json`, you _must_ run
21-
`go generate ./signerserver` to re-generate the client code in the `api/`
22-
directory.
14+
If there are changes in `spec/filtered-openapi.json`, the `go generate ./signerserver` _must_ be run to re-generate the client code in the `api/` directory.
2315

2416
## Testing
2517

26-
Currently, the only way to test the code is using
27-
[this PR](https://github.com/ava-labs/avalanchego/pull/3725) where you have to
28-
set the `--staking-rpc-signer=127.0.0.1:50051` configuration flag. You must
29-
first start this application before starting the `avalanchego` node.
18+
The `cube-signer-sidecar` depends on the AvalancheGo changes implemented in [this PR](https://github.com/ava-labs/avalanchego/pull/3965). In order to test it, set the `--staking-rpc-signer-endpoint=127.0.0.1:50051` configuration flag, and ensure that the `cube-signer-sidecar` application is running before starting the `avalanchego` node.
3019

3120
## Running
3221

3322
### Key Creation
3423

35-
To run the `cubist-signer` locally, you will need a
36-
[`CubeSigner`](https://github.com/cubist-partners/CubeSigner/) application
37-
(sorry for the similar names). If you need an invite to see the repository,
38-
please reach out to someone on the @ava-labs/interop team. Once installed, you
39-
will need to set up the `CubeSigner` and login following
40-
[the Getting Started instructions](https://signer-docs.cubist.dev/getting-started)
41-
(the docs are password protected, the password should be in the `CubeSigner`
42-
README). After, you will need to create a `role` with the following command:
24+
The [`CubeSigner`](https://github.com/cubist-partners/CubeSigner/) application is needed to set up the `cube-signer-sidecar` to be run locally. Once installed, set up the `CubeSigner` and log in following the [Getting Started instructions](https://signer-docs.cubist.dev/getting-started). The following commands can then be used to set up a role, key, and signing policy.
4325

4426
```shell
27+
# Create a role.
4528
cs role create --role-name bls_signer
46-
```
47-
48-
Then create a key:
4929

50-
```shell
30+
# Create a key.
5131
cs keys create --key-type=bls-ava-icm
52-
```
5332

54-
Next, set the signing policy on the key:
55-
56-
```shell
57-
# you can find the <key_id> using `cs keys list`
33+
# Set the signing policy for the key.
34+
# The `cs keys list` command can be used to find the <key_id>.
5835
cs key set-policy --key-id <key_id> --policy '"AllowRawBlobSigning"'
59-
```
60-
61-
After, you need to add the key to the role
6236

63-
```shell
64-
# you can either use the full <role_id> or use the <role_name> ("bls_signer" from above, for example)
37+
# Add the key to the role.
38+
# Either the full <role_id> or the <role_name> (i.e. "bls_signer") can be used.
6539
cs role add-key --role-id <role_id> --key-id <key_id>
66-
```
6740

68-
Finally, you can create a token file associated with the role that you created:
69-
70-
```shell
41+
# Finally, create a token file associated with the role.
7142
cs token create --role-id <role_id> > <path_to_token_file>.json
7243
```
7344

7445
### Configuration
7546

7647
Below is a list of configuration options that can be set via a JSON config file passed in via `--config-file` flag or set through environment variables or flags. To get the environment variable corresponding to the key uppercase the key and change the delimiter from "-" to "_". The following precedence order is used, with each item taking precedence over items below it:
48+
7749
1. Flags
7850
2. Environment variables
7951
3. Config file
8052

8153
- `"token-file-path": string` (required)
8254

83-
This is the relative path (absolute paths also work) to the token file, we
84-
created from the last step above.
55+
This is the path to the token file, created in the last step above.
8556

86-
The `refresh-token` (part of the JSON output of `cs token create`) has a very
87-
short TTL by default, so you must start the signer (this repo) before it
88-
expires. Once started, your `<path_to_token>.json` file will be continuously
89-
refreshed as needed. To change any of the default token parameters, see
90-
`cs token create --help`.
57+
The `refresh-token` (part of the JSON output of `cs token create`) has a short TTL by default, and the `cube-signer-sidecar` must be started before it expires. Once started, the `<path_to_token>.json` file will be continuously refreshed as needed. To change any of the default token parameters, see `cs token create --help`.
9158

9259
- `"signer-endpoint": string` (required)
9360

94-
This is the Cubist-API endpoint.
61+
The CubeSigner API endpoint.
9562

9663
- `"key-id": string` (required)
9764

98-
The `cubist-signer` (this repo) can only use one key at a time as an
99-
`avalanchego` validator is only meant to have a single BLS signing key.
100-
Specifying the `KEY_ID` is how the Cubist-API knows what key to use for
101-
signing. The `role` associated with the `role_id` filed in the token JSON will
102-
need access to this key (see [Configuration](#configuration))
65+
The `cube-signer-sidecar` can only use one key at a time, as an `avalanchego` validator is only meant to have a single BLS signing key. Specifying the `KEY_ID` is how the CubeSigner API knows what key to use for signing. The `role` associated with the `role_id` filed in the token JSON will need access to this key (see [Configuration](#configuration)).
10366

10467
- `"port": int` (defaults to 50051)
10568

106-
Port at which to start the local signer server.
69+
The port at which to start the local signer server.
10770

10871
### Usage
10972

110-
Both the `SIGNER_ENDPOINT` and `KEY_ID` can be exported in your current shell
111-
session as they are unlikely to change if you running the signer locally.
73+
Both the `SIGNER_ENDPOINT` and `KEY_ID` can be exported in the current shell session as they are unlikely to change if running the signer locally.
11274

11375
```bash
11476
export SIGNER_ENDPOINT=https://gamma.signer.cubist.dev
@@ -120,11 +82,8 @@ TOKEN_FILE_PATH="./token.json" go run main/main.go
12082
### E2E tests
12183

12284
#### Running Locally
123-
To run E2E locally follow the steps in [Key Creation](#key-creation) above to generate a new key associated with the `e2e_signer` role and generate a session token file saved as `e2e_session.json`. After that the tests can be run via
85+
To run E2E locally follow the [key creation](#key-creation) steps above to generate a new key associated with the `e2e_signer` role, and generate a session token file saved as `e2e_session.json`. After that the tests can be run via
12486

12587
```bash
12688
./scripts/e2e_test.sh
12789
```
128-
129-
#### CI
130-
The base64 encoded session token is stored in Github secrets. It currently expires on 6/05/2026. When it expires, a new token can be generated and stored there.

api/client.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/types.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/keys.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const (
1919
)
2020

2121
func BuildFlagSet() *pflag.FlagSet {
22-
fs := pflag.NewFlagSet("cubist-signer", pflag.ExitOnError)
22+
fs := pflag.NewFlagSet("cube-signer-sidecar", pflag.ExitOnError)
2323
fs.Bool(HelpKey, false, "Display this help message and exit")
2424
fs.String(ConfigFileKey, "", "Path to the config file")
2525

go.mod

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
module github.com/ava-labs/cubist-signer
1+
module github.com/ava-labs/cube-signer-sidecar
22

3-
go 1.23.10
3+
go 1.23.11
44

55
require (
66
github.com/ava-labs/avalanchego v1.13.2
7-
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1
8-
github.com/oapi-codegen/runtime v1.1.1
7+
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0
8+
github.com/oapi-codegen/runtime v1.1.2
99
github.com/onsi/ginkgo/v2 v2.23.4
1010
github.com/onsi/gomega v1.37.0
1111
github.com/spf13/pflag v1.0.6
1212
github.com/spf13/viper v1.20.1
1313
github.com/stretchr/testify v1.10.0
1414
go.uber.org/mock v0.5.2
15-
go.uber.org/zap v1.26.0
15+
go.uber.org/zap v1.27.0
1616
google.golang.org/grpc v1.73.0
1717
)
1818

@@ -22,7 +22,7 @@ require (
2222
github.com/davecgh/go-spew v1.1.1 // indirect
2323
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
2424
github.com/fsnotify/fsnotify v1.8.0 // indirect
25-
github.com/getkin/kin-openapi v0.131.0 // indirect
25+
github.com/getkin/kin-openapi v0.132.0 // indirect
2626
github.com/go-logr/logr v1.4.2 // indirect
2727
github.com/go-openapi/jsonpointer v0.21.0 // indirect
2828
github.com/go-openapi/swag v0.23.0 // indirect
@@ -41,7 +41,8 @@ require (
4141
github.com/pmezard/go-difflib v1.0.0 // indirect
4242
github.com/sagikazarmark/locafero v0.7.0 // indirect
4343
github.com/sourcegraph/conc v0.3.0 // indirect
44-
github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect
44+
github.com/speakeasy-api/jsonpath v0.6.0 // indirect
45+
github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect
4546
github.com/spf13/afero v1.12.0 // indirect
4647
github.com/spf13/cast v1.7.1 // indirect
4748
github.com/subosito/gotenv v1.6.0 // indirect

go.sum

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
2323
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
2424
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
2525
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
26-
github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE=
27-
github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
26+
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
27+
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
2828
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
2929
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
3030
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -81,10 +81,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd
8181
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
8282
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
8383
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
84-
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q=
85-
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8=
86-
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
87-
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
84+
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 h1:iJvF8SdB/3/+eGOXEpsWkD8FQAHj6mqkb6Fnsoc8MFU=
85+
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0/go.mod h1:fwlMxUEMuQK5ih9aymrxKPQqNm2n8bdLk1ppjH+lr9w=
86+
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
87+
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
8888
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
8989
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
9090
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
@@ -120,8 +120,10 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
120120
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
121121
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
122122
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
123-
github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg=
124-
github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc=
123+
github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8=
124+
github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
125+
github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU=
126+
github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg=
125127
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
126128
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
127129
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
@@ -166,8 +168,8 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
166168
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
167169
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
168170
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
169-
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
170-
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
171+
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
172+
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
171173
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
172174
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
173175
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

main/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
"strconv"
1010

1111
"github.com/ava-labs/avalanchego/proto/pb/signer"
12-
"github.com/ava-labs/cubist-signer/api"
13-
"github.com/ava-labs/cubist-signer/config"
14-
"github.com/ava-labs/cubist-signer/signerserver"
12+
"github.com/ava-labs/cube-signer-sidecar/api"
13+
"github.com/ava-labs/cube-signer-sidecar/config"
14+
"github.com/ava-labs/cube-signer-sidecar/signerserver"
1515
"google.golang.org/grpc"
1616
)
1717

mockapi/mockclient.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)