bridge: verify LockupObservation signature
Final missing piece of the aggregation mechanism - signatures are now verified before storing them in the aggregation. ghstack-source-id: 3bb57c488623454370ad1bf2a38e1278c65ce9e0 Pull Request resolved: https://github.com/certusone/wormhole/pull/59
This commit is contained in:
parent
55fd671228
commit
45d10618ce
|
@ -152,12 +152,42 @@ func vaaConsensusProcessor(lockC chan *common.ChainLock, setC chan *common.Guard
|
||||||
|
|
||||||
state.vaaSignatures[hash].ourVAA = v
|
state.vaaSignatures[hash].ourVAA = v
|
||||||
case m := <-obsvC:
|
case m := <-obsvC:
|
||||||
|
// SECURITY: at this point, observations received from the p2p network are fully untrusted (all fields!)
|
||||||
|
//
|
||||||
|
// Note that observations are never tied to the (verified) p2p identity key - the p2p network
|
||||||
|
// identity is completely decoupled from the guardian identity, p2p is just transport.
|
||||||
|
|
||||||
logger.Info("received lockup observation",
|
logger.Info("received lockup observation",
|
||||||
zap.String("digest", hex.EncodeToString(m.Hash)),
|
zap.String("digest", hex.EncodeToString(m.Hash)),
|
||||||
zap.String("signature", hex.EncodeToString(m.Signature)),
|
zap.String("signature", hex.EncodeToString(m.Signature)),
|
||||||
zap.String("addr", hex.EncodeToString(m.Addr)))
|
zap.String("addr", hex.EncodeToString(m.Addr)))
|
||||||
|
|
||||||
|
// Verify the Guardian's signature. This verifies that m.Signature matches m.Hash and recovers
|
||||||
|
// the public key that was used to sign the payload.
|
||||||
|
pk, err := crypto.Ecrecover(m.Hash, m.Signature)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("failed to verify signature on lockup observation",
|
||||||
|
zap.String("digest", hex.EncodeToString(m.Hash)),
|
||||||
|
zap.String("signature", hex.EncodeToString(m.Signature)),
|
||||||
|
zap.String("addr", hex.EncodeToString(m.Addr)),
|
||||||
|
zap.Error(err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that m.Addr matches the public key that signed m.Hash.
|
||||||
their_addr := ethcommon.BytesToAddress(m.Addr)
|
their_addr := ethcommon.BytesToAddress(m.Addr)
|
||||||
|
signer_pk := ethcommon.BytesToAddress(crypto.Keccak256(pk[1:])[12:])
|
||||||
|
|
||||||
|
if their_addr != signer_pk {
|
||||||
|
logger.Info("invalid lockup observation - address does not match pubkey",
|
||||||
|
zap.String("digest", hex.EncodeToString(m.Hash)),
|
||||||
|
zap.String("signature", hex.EncodeToString(m.Signature)),
|
||||||
|
zap.String("addr", hex.EncodeToString(m.Addr)),
|
||||||
|
zap.String("pk", signer_pk.Hex()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that m.Addr is included in the current guardian set.
|
||||||
_, ok := gs.KeyIndex(their_addr)
|
_, ok := gs.KeyIndex(their_addr)
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Warn("received observation by unknown guardian - is our guardian set outdated?",
|
logger.Warn("received observation by unknown guardian - is our guardian set outdated?",
|
||||||
|
@ -167,13 +197,22 @@ func vaaConsensusProcessor(lockC chan *common.ChainLock, setC chan *common.Guard
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hooray! Now, we have verified all fields on LockupObservation and know that it includes
|
||||||
|
// a valid signature by an active guardian. We still don't fully trust them, as they may be
|
||||||
|
// byzantine, but now we know who we're dealing with.
|
||||||
|
|
||||||
// TODO: timeout/garbage collection for lockup state
|
// TODO: timeout/garbage collection for lockup state
|
||||||
// TODO: rebroadcast signatures for VAAs that fail to reach consensus
|
// TODO: rebroadcast signatures for VAAs that fail to reach consensus
|
||||||
|
|
||||||
// []byte isn't hashable in a map. Paying a small extra cost to for encoding for easier debugging.
|
// []byte isn't hashable in a map. Paying a small extra cost for encoding for easier debugging.
|
||||||
hash := hex.EncodeToString(m.Hash)
|
hash := hex.EncodeToString(m.Hash)
|
||||||
|
|
||||||
if state.vaaSignatures[hash] == nil {
|
if state.vaaSignatures[hash] == nil {
|
||||||
|
// We haven't yet seen this lockup ourselves, and therefore do not know what the VAA looks like.
|
||||||
|
// However, we have established that a valid guardian has signed it, and therefore we can
|
||||||
|
// already start aggregating signatures for it.
|
||||||
|
//
|
||||||
|
// TODO: a malicious guardian can DoS this by creating fake lockups
|
||||||
state.vaaSignatures[hash] = &vaaState{
|
state.vaaSignatures[hash] = &vaaState{
|
||||||
firstObserved: time.Now(),
|
firstObserved: time.Now(),
|
||||||
signatures: map[ethcommon.Address][]byte{},
|
signatures: map[ethcommon.Address][]byte{},
|
||||||
|
@ -182,11 +221,10 @@ func vaaConsensusProcessor(lockC chan *common.ChainLock, setC chan *common.Guard
|
||||||
|
|
||||||
state.vaaSignatures[hash].signatures[their_addr] = m.Signature
|
state.vaaSignatures[hash].signatures[their_addr] = m.Signature
|
||||||
|
|
||||||
// Enumerate guardian set and check for signatures
|
// Aggregate all valid signatures into a list of vaa.Signature and construct signed VAA.
|
||||||
agg := make([]bool, len(gs.Keys))
|
agg := make([]bool, len(gs.Keys))
|
||||||
var sigs []*vaa.Signature
|
var sigs []*vaa.Signature
|
||||||
for i, a := range gs.Keys {
|
for i, a := range gs.Keys {
|
||||||
// TODO: verify signature
|
|
||||||
s, ok := state.vaaSignatures[hash].signatures[a]
|
s, ok := state.vaaSignatures[hash].signatures[a]
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
|
|
Loading…
Reference in New Issue