Browse Source

Verify validity of account_info using signature

xno
codesoap 1 year ago
parent
commit
8f33306d8a
  1. 7
      README.md
  2. 40
      account.go
  3. 2
      balance.go
  4. 38
      block.go
  5. 46
      ed25519.go

7
README.md

@ -81,10 +81,9 @@ and ensure, that it does nothing you wouldn't want it to do. @@ -81,10 +81,9 @@ and ensure, that it does nothing you wouldn't want it to do.
To change some defaults, take a look at `config.go`.
Signatures are created without the help of a node, to avoid your seed or
private keys being stolen by a node operator. Apparently this is not to
be taken for granted, since the node API offers, for example, a [method
for signing](https://docs.nano.org/commands/rpc-protocol/#sign) your
blocks.
private keys being stolen by a node operator. The received account info
is always validated using block signatures to ensure the node operator
cannot manipulate atto by, for example, reporting wrong balances.
atto does not have any persistance and writes nothing to your
file system. This makes atto very portable, but also means, that

40
account.go

@ -12,6 +12,11 @@ type accountInfo struct { @@ -12,6 +12,11 @@ type accountInfo struct {
Balance string `json:"balance"`
}
type blockInfo struct {
Error string `json:"error"`
Contents block `json:"contents"`
}
func getAccountInfo(address string) (info accountInfo, err error) {
requestBody := fmt.Sprintf(`{`+
`"action": "account_info",`+
@ -33,6 +38,41 @@ func getAccountInfo(address string) (info accountInfo, err error) { @@ -33,6 +38,41 @@ func getAccountInfo(address string) (info accountInfo, err error) {
info.Balance = "0"
} else if info.Error != "" {
err = fmt.Errorf("could not fetch balance: %s", info.Error)
return
}
err = verifyInfo(info, address)
return
}
// verifyInfo gets the frontier block of info, ensures that Hash,
// Representative and Balance match and verifies it's signature.
func verifyInfo(info accountInfo, address string) error {
requestBody := fmt.Sprintf(`{`+
`"action": "block_info",`+
`"json_block": "true",`+
`"hash": "%s"`+
`}`, info.Frontier)
responseBytes, err := doRPC(requestBody)
if err != nil {
return err
}
var block blockInfo
if err = json.Unmarshal(responseBytes, &block); err != nil {
return err
}
if info.Error != "" {
return fmt.Errorf("could not get block info: %s", info.Error)
}
publicKey, err := getPublicKeyFromAddress(address)
if err != nil {
return err
}
if err = block.Contents.verifySignature(publicKey); err == errInvalidSignature ||
info.Frontier != block.Contents.Hash ||
info.Representative != block.Contents.Representative ||
info.Balance != block.Contents.Balance {
return fmt.Errorf("the received account info has been manipulated; " +
"change your node immediately!")
}
return err
}

2
balance.go

@ -106,7 +106,7 @@ func receivePendingSends(info accountInfo, privateKey *big.Int) (updatedBalance @@ -106,7 +106,7 @@ func receivePendingSends(info accountInfo, privateKey *big.Int) (updatedBalance
}
fmt.Fprintln(os.Stderr, "done")
previousBlock = block.Hash
previousBlock = block.Hash // Hash was computed during signing.
}
return
}

38
block.go

@ -9,6 +9,8 @@ import ( @@ -9,6 +9,8 @@ import (
"golang.org/x/crypto/blake2b"
)
var errInvalidSignature = fmt.Errorf("invalid block signature")
type block struct {
Type string `json:"type"`
Account string `json:"account"`
@ -19,6 +21,7 @@ type block struct { @@ -19,6 +21,7 @@ type block struct {
Signature string `json:"signature"`
Work string `json:"work"`
Hash string `json:"-"`
HashBytes []byte `json:"-"`
}
type workGenerateResponse struct {
@ -28,12 +31,10 @@ type workGenerateResponse struct { @@ -28,12 +31,10 @@ type workGenerateResponse struct {
func (b *block) sign(privateKey *big.Int) error {
publicKey := derivePublicKey(privateKey)
hash, err := b.hash(publicKey)
if err != nil {
if err := b.addHashIfUnhashed(publicKey); err != nil {
return err
}
b.Hash = fmt.Sprintf("%064X", hash)
signature, err := sign(privateKey, hash)
signature, err := sign(privateKey, b.HashBytes)
if err != nil {
return err
}
@ -41,8 +42,35 @@ func (b *block) sign(privateKey *big.Int) error { @@ -41,8 +42,35 @@ func (b *block) sign(privateKey *big.Int) error {
return nil
}
func (b *block) verifySignature(publicKey *big.Int) (err error) {
if err = b.addHashIfUnhashed(publicKey); err != nil {
return
}
sig, ok := big.NewInt(0).SetString(b.Signature, 16)
if !ok {
return fmt.Errorf("cannot parse '%s' as an integer", b.Signature)
}
if !isValidSignature(publicKey, b.HashBytes, bigIntToBytes(sig, 64)) {
err = errInvalidSignature
}
return
}
func (b *block) addHashIfUnhashed(publicKey *big.Int) error {
if b.Hash == "" || len(b.HashBytes) == 0 {
hashBytes, err := b.hash(publicKey)
if err != nil {
return err
}
b.HashBytes = hashBytes
b.Hash = fmt.Sprintf("%064X", b.HashBytes)
}
return nil
}
func (b *block) hash(publicKey *big.Int) ([]byte, error) {
// Look at https://nanoo.tools/block for a reference.
// See https://nanoo.tools/block for a reference.
msg := make([]byte, 176, 176)
msg[31] = 0x6 // block preamble

46
ed25519.go

@ -48,3 +48,49 @@ func sign(privateKey *big.Int, msg []byte) ([]byte, error) { @@ -48,3 +48,49 @@ func sign(privateKey *big.Int, msg []byte) ([]byte, error) {
return signature, nil
}
func isValidSignature(publicKey *big.Int, msg, sig []byte) bool {
// This implementation based on the one from github.com/iotaledger/iota.go.
publicKeyBytes := bigIntToBytes(publicKey, 32)
// ZIP215: this works because SetBytes does not check that encodings are canonical
A, err := new(edwards25519.Point).SetBytes(publicKeyBytes)
if err != nil {
return false
}
A.Negate(A)
h, err := blake2b.New512(nil)
if err != nil {
return false
}
h.Write(sig[:32])
h.Write(publicKeyBytes)
h.Write(msg)
var digest [64]byte
h.Sum(digest[:0])
hReduced := new(edwards25519.Scalar).SetUniformBytes(digest[:])
// ZIP215: this works because SetBytes does not check that encodings are canonical
checkR, err := new(edwards25519.Point).SetBytes(sig[:32])
if err != nil {
return false
}
// https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
// the range [0, order) in order to prevent signature malleability
s, err := new(edwards25519.Scalar).SetCanonicalBytes(sig[32:])
if err != nil {
return false
}
R := new(edwards25519.Point).VarTimeDoubleScalarBaseMult(hReduced, A, s)
// ZIP215: We want to check [8](R - checkR) == 0
p := new(edwards25519.Point).Subtract(R, checkR) // p = R - checkR
p.Add(p, p) // p = [2]p
p.Add(p, p) // p = [4]p
p.Add(p, p) // p = [8]p
return p.Equal(edwards25519.NewIdentityPoint()) == 1 // p == 0
}

Loading…
Cancel
Save