Public parameters:
- G: group defined by curve25519
- q: order of the group
- B: generator of the group
- Key generation:
- Each party choose a random secret key
sk
from the field and hashes it with SHA512 to get an extended private key(x||prefix)
:- h = SHA512(sk)
- private key x = h[0:32] with bits255 = 0, bits254 = 1 and bits2 = bits1 = bits0 = 0
- prefix = h[32:64]
- public key A = xB
- Each party choose a random secret key
- Key aggregation:
- Aggregated public key
apk
= hash(A0, A1) * A0 + hash(A1, A0) * A1
- Aggregated public key
- Signing:
- Round1: Each party:
- choose a random 256 bits number zi
- ri = SHA512(prefixi || M || zi) where prefixi is the prefix of i's extended private key and M is the message hash
- Ri = (ri mod q)B
- calculate commitment ti = hash(Ri)
- send ti to the other party
- Round2: Each party:
- send Ri to the other party
- when R1-i received, check t1-i == hash(R1-i)
- Round3: Each party:
- compute aggregated public key
apk
- compute ai = hash(Ai, A1-i)
- compute R = R0 + R1 and
k
= hash(R, apk, M) - compute si = ri + k * xi * ai mod q
- send si to the other party and receive s1-i
- signature s = si + s1-i
- compute aggregated public key
- Round1: Each party:
- Verification
- check 8sB == 8R + 8k*apk, same as the traditional Ed25519 signature scheme
We implemented the scheme in Rust, with a client and a server:
Key generation:
Signing:
In the semi-honest model, a semi-honest party will perform computations and send messages as instructed by the protocol, but with the exception that it keeps all the intermediate computations and tries to get the other party's private information.
- Key generation phase:
- Client generates an Ed25519 key pair locally, sends the
keygen
request to the server along with its public key - Server generates an Ed25519 key pair locally, replay the client with its public key
- Both party perform a key aggregation operation using its own public key and the other party's public key
As we can see, both party reveals only its Ed25519 public key in the process, Ed25519 signature scheme has a 2^128 security target, which means currently it's computationally impossible to get the private key from a public key.
Signing phase:
- Client generates a random 256 bit number z0, compute r0, R0 and commitment t0, send a sign request to server along with t0 and the hash of the message to be signed
- Server generates a random 256 bit number z1, compute r1, R1 and commitment t1, replay the client with t1
- Client start the decommit round, send R0 to the server
- Server check commitment t0, if commitment valid, generate shared secret k, then sign the message with k and r1, send the signature s1 and commitment t1 back to the client
- Client check commitment t1, if valid, generate shared secret k, generate s0 with k and r0
- Aggregate s0 and s1 to get the final signature
s
, then check ifs
is valid, output the validity of the signature along with it. Note thats
is valid implies si is valid.
If the commitment checks failed in the process, we simply abort the signing process and no signature can be generated. If the final signature check failed, we still output it, but mark it as invalid.
The commitment round makes sure no party can change its chosen value zi in the process, so that the shared secret k can be generated. Each party signs the message with k and ri, then we can simply add s0 and s1 to get the final aggrated signaute s
since Ed25519 signature is linear.
In the signing process, values revealed to generate the shared secret k are ti, Ri and si, sensitive information like zi and ri is never exposed.
zi is used to generate ri, and Ri = (ri mod q)B, a semi-honest party has to solve the discrete log problem on elliptic curve to get ri from Ri, which is computationally impossible for now.
Note that this is a non-deterministic signing scheme because zi is randomly chosen for each sign operation, this is secure as long as the randomness is well chosen.
This is just an intuitive explanation on why this sheme is secure against the semi-honest model, for formal proof of security, refer to Lindell's Fast Secure Two-Party ECDSA Signing in which he described a two-party ECDSA signing scheme similar to this one and provided both game based and simulation based security proof, also refer to Compact Multi-Signatures for Smaller Blockchains in which the author proved the security of this scheme in multi-party condition using forking lemma.
Build:
cargo build --release
Start server:
./target/release/server --port 8000 --keyfile-path server_keys
The server will listen at port 8000 and the server side key pair will be stored in server_keys
folder.
Generate key:
➜ two-party-eddsa git:(tcp) ./target/release/client --host 127.0.0.1:8000 --keyfile client.key --keygen
aggregated_pubkey: "2c8bdca481bfa4973866b62c2c8ed272975c28939abeb528fda1e7a686a3ad5b"
elapsed_time: 7 ms
The client key pair will be stored in the file client.key
Sign a message:
➜ two-party-eddsa git:(tcp) ./target/release/client --sign --msg "hello world" --host 127.0.0.1:8000 --keyfile client.key
R: "302ac5eaf318a47f49f6e58c1134bc697ea4a56b4e0b2285a4c3f916dee58734"
s: "b1b038c841ef2c6fa155f20b40a354fbb9635895118cf3947756d8c0f6774d03"
signature valid
elapsed_time: 6 ms
Verify the signature:
➜ two-party-eddsa git:(tcp) ./target/release/client --verify --msg "hello world" --sig-r 302ac5eaf318a47f49f6e58c1134bc697ea4a56b4e0b2285a4c3f916dee58734 --sig-s b1b038c841ef2c6fa155f20b40a354fbb9635895118cf3947756d8c0f6774d03 --pubkey 2c8bdca481bfa4973866b62c2c8ed272975c28939abeb528fda1e7a686a3ad5b
true
elapsed_time: 1 ms
Generate key bench:
➜ two-party-eddsa git:(tcp) ./target/release/client --host 127.0.0.1:8000 --bench-gen 100
aggregated_pubkey: "821c0fa06db050827722f12d2c020e06effd6e6901a335bc8e766b2504ac912c"
...
aggregated_pubkey: "978b4812f8824cc0445ac196cb2766ff60b2cc23c70e304f306094a50ee5a997"
bench keygen: 100 keys in 393 ms, 3.93 ms per key
Sign bench:
➜ two-party-eddsa git:(tcp) ./target/release/client --host 127.0.0.1:8000 --bench-sign 100 --keyfile client.key
R: "b770feee73b4461301883d84147d549b94c7515617eac9d38666be408c7b4068"
s: "48118cbe4c77bfaf7f8513b10f974ec32ab6a850e5ecc08df33774422a461501"
signature valid
...
R: "840b23f29835133f6fbd146439a2efd3ada8e60008948bebc40b2492e4d1e7cd"
s: "0def078197863cdcb2be16f20fed49c6f641b5c1564e06480e7c0ef7a02cf007"
signature valid
bench sign: 100 signs in 503 ms, 5.03 ms per sign
Environment: 2 machines:
-
12 physical cores (Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz)
-
128G memory
-
network delay ~0 ms
-
both using single core
Test result:
bench keygen: 1000 keys in 7567 ms, 7.567 ms per key
bench sign: 1000 signs in 5404 ms, 5.404 ms per sign