Browse Source

Introduce account struct to simplify code

xno
codesoap 10 months ago
parent
commit
3aeeef59b5
  1. 87
      account.go
  2. 54
      address.go
  3. 26
      balance.go
  4. 26
      block.go
  5. 20
      change.go
  6. 13
      ed25519.go
  7. 32
      send.go
  8. 17
      util.go

87
account.go

@ -1,13 +1,25 @@ @@ -1,13 +1,25 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"math/big"
"os"
"strings"
"filippo.io/edwards25519"
"golang.org/x/crypto/blake2b"
)
var errAccountNotFound = fmt.Errorf("account has not yet been opened")
type account struct {
privateKey *big.Int
publicKey *big.Int
address string
}
type accountInfo struct {
Error string `json:"error"`
Frontier string `json:"frontier"`
@ -20,16 +32,75 @@ type blockInfo struct { @@ -20,16 +32,75 @@ type blockInfo struct {
Contents block `json:"contents"`
}
func getAccountInfo(privateKey *big.Int) (info accountInfo, err error) {
address, err := getAddress(privateKey)
// ownAccount initializes the own account using the seed provided via
// standard input and accountIndexFlag.
func ownAccount() (a account, err error) {
seed, err := getSeed()
if err != nil {
return
}
a.privateKey = getPrivateKey(seed, uint32(accountIndexFlag))
a.publicKey = derivePublicKey(a.privateKey)
a.address, err = getAddress(a.publicKey)
return
}
// getSeed takes the first line of the standard input and interprets it
// as a hexadecimal representation of a 32byte seed.
func getSeed() (*big.Int, error) {
in := bufio.NewReader(os.Stdin)
firstLine, err := in.ReadString('\n')
if err != nil {
return nil, err
}
seed, ok := big.NewInt(0).SetString(strings.TrimSpace(firstLine), 16)
if !ok {
return nil, fmt.Errorf("could not parse seed")
}
return seed, nil
}
func getPrivateKey(seed *big.Int, index uint32) *big.Int {
seedBytes := bigIntToBytes(seed, 32)
indexBytes := bigIntToBytes(big.NewInt(int64(index)), 4)
in := append(seedBytes, indexBytes...)
privateKeyBytes := blake2b.Sum256(in)
return big.NewInt(0).SetBytes(privateKeyBytes[:])
}
func derivePublicKey(privateKey *big.Int) *big.Int {
hashBytes := blake2b.Sum512(bigIntToBytes(privateKey, 32))
scalar := edwards25519.NewScalar().SetBytesWithClamping(hashBytes[:32])
publicKeyBytes := edwards25519.NewIdentityPoint().ScalarBaseMult(scalar).Bytes()
return big.NewInt(0).SetBytes(publicKeyBytes)
}
func getAddress(publicKey *big.Int) (string, error) {
base32PublicKey := base32Encode(publicKey)
hasher, err := blake2b.New(5, nil)
if err != nil {
return "", err
}
publicKeyBytes := bigIntToBytes(publicKey, 32)
if _, err := hasher.Write(publicKeyBytes); err != nil {
return "", err
}
hashBytes := hasher.Sum(nil)
base32Hash := base32Encode(big.NewInt(0).SetBytes(revertBytes(hashBytes)))
address := "nano_" +
strings.Repeat("1", 52-len(base32PublicKey)) + base32PublicKey +
strings.Repeat("1", 8-len(base32Hash)) + base32Hash
return address, nil
}
func (a account) getInfo() (info accountInfo, err error) {
requestBody := fmt.Sprintf(`{`+
`"action": "account_info",`+
`"account": "%s",`+
`"representative": "true"`+
`}`, address)
`}`, a.address)
responseBytes, err := doRPC(requestBody)
if err != nil {
return
@ -44,14 +115,14 @@ func getAccountInfo(privateKey *big.Int) (info accountInfo, err error) { @@ -44,14 +115,14 @@ func getAccountInfo(privateKey *big.Int) (info accountInfo, err error) {
} else if info.Error != "" {
err = fmt.Errorf("could not fetch account info: %s", info.Error)
} else {
err = verifyInfo(info, address)
err = a.verifyInfo(info)
}
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 {
func (a account) verifyInfo(info accountInfo) error {
requestBody := fmt.Sprintf(`{`+
`"action": "block_info",`+
`"json_block": "true",`+
@ -68,11 +139,7 @@ func verifyInfo(info accountInfo, address string) error { @@ -68,11 +139,7 @@ func verifyInfo(info accountInfo, address string) error {
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 ||
if err = block.Contents.verifySignature(a); err == errInvalidSignature ||
info.Frontier != block.Contents.Hash ||
info.Representative != block.Contents.Representative ||
info.Balance != block.Contents.Balance {

54
address.go

@ -3,60 +3,14 @@ package main @@ -3,60 +3,14 @@ package main
import (
"fmt"
"math/big"
"strings"
"filippo.io/edwards25519"
"golang.org/x/crypto/blake2b"
)
func printAddress() error {
seed, err := getSeed()
if err != nil {
return err
}
privateKey := getPrivateKey(seed, uint32(accountIndexFlag))
address, err := getAddress(privateKey)
if err != nil {
return err
}
fmt.Println(address)
return nil
}
func getAddress(privateKey *big.Int) (string, error) {
publicKey := derivePublicKey(privateKey)
base32PublicKey := base32Encode(publicKey)
hasher, err := blake2b.New(5, nil)
if err != nil {
return "", err
account, err := ownAccount()
if err == nil {
fmt.Println(account.address)
}
publicKeyBytes := bigIntToBytes(publicKey, 32)
if _, err := hasher.Write(publicKeyBytes); err != nil {
return "", err
}
hashBytes := hasher.Sum(nil)
base32Hash := base32Encode(big.NewInt(0).SetBytes(revertBytes(hashBytes)))
address := "nano_" +
strings.Repeat("1", 52-len(base32PublicKey)) + base32PublicKey +
strings.Repeat("1", 8-len(base32Hash)) + base32Hash
return address, nil
}
func getPrivateKey(seed *big.Int, index uint32) *big.Int {
seedBytes := bigIntToBytes(seed, 32)
indexBytes := bigIntToBytes(big.NewInt(int64(index)), 4)
in := append(seedBytes, indexBytes...)
privateKeyBytes := blake2b.Sum256(in)
return big.NewInt(0).SetBytes(privateKeyBytes[:])
}
func derivePublicKey(privateKey *big.Int) *big.Int {
hashBytes := blake2b.Sum512(bigIntToBytes(privateKey, 32))
scalar := edwards25519.NewScalar().SetBytesWithClamping(hashBytes[:32])
publicKeyBytes := edwards25519.NewIdentityPoint().ScalarBaseMult(scalar).Bytes()
return big.NewInt(0).SetBytes(publicKeyBytes)
return err
}
func getPublicKeyFromAddress(address string) (*big.Int, error) {

26
balance.go

@ -34,13 +34,11 @@ type pendingBlockSource struct { @@ -34,13 +34,11 @@ type pendingBlockSource struct {
}
func printBalance() error {
seed, err := getSeed()
account, err := ownAccount()
if err != nil {
return err
}
privateKey := getPrivateKey(seed, uint32(accountIndexFlag))
info, err := getAccountInfo(privateKey)
info, err := account.getInfo()
if err == errAccountNotFound {
// This info is needed to create the first block:
info.Frontier = "0000000000000000000000000000000000000000000000000000000000000000"
@ -49,7 +47,7 @@ func printBalance() error { @@ -49,7 +47,7 @@ func printBalance() error {
} else if err != nil {
return err
}
updatedBalance, err := receivePendingSends(info, privateKey)
updatedBalance, err := account.receivePendingSends(info)
if err != nil {
return err
}
@ -57,17 +55,13 @@ func printBalance() error { @@ -57,17 +55,13 @@ func printBalance() error {
return nil
}
func receivePendingSends(info accountInfo, privateKey *big.Int) (updatedBalance *big.Int, err error) {
func (a account) receivePendingSends(info accountInfo) (updatedBalance *big.Int, err error) {
updatedBalance, ok := big.NewInt(0).SetString(info.Balance, 10)
if !ok {
err = fmt.Errorf("cannot parse '%s' as an integer", info.Balance)
return
}
address, err := getAddress(privateKey)
if err != nil {
return
}
sends, err := getPendingSends(address)
sends, err := a.getPendingSends()
if err != nil {
return
}
@ -84,16 +78,16 @@ func receivePendingSends(info accountInfo, privateKey *big.Int) (updatedBalance @@ -84,16 +78,16 @@ func receivePendingSends(info accountInfo, privateKey *big.Int) (updatedBalance
block := block{
Type: "state",
Account: address,
Account: a.address,
Previous: previousBlock,
Representative: info.Representative,
Balance: updatedBalance.String(),
Link: blockHash,
}
if err = block.sign(privateKey); err != nil {
if err = block.sign(a); err != nil {
return
}
if err = block.addWork(receiveWorkThreshold, privateKey); err != nil {
if err = block.addWork(receiveWorkThreshold, a); err != nil {
return
}
process := process{
@ -112,13 +106,13 @@ func receivePendingSends(info accountInfo, privateKey *big.Int) (updatedBalance @@ -112,13 +106,13 @@ func receivePendingSends(info accountInfo, privateKey *big.Int) (updatedBalance
return
}
func getPendingSends(address string) (sends pendingBlocks, err error) {
func (a account) getPendingSends() (sends pendingBlocks, err error) {
requestBody := fmt.Sprintf(`{`+
`"action": "pending", `+
`"account": "%s", `+
`"include_only_confirmed": "true", `+
`"source": "true"`+
`}`, address)
`}`, a.address)
responseBytes, err := doRPC(requestBody)
if err != nil {
return

26
block.go

@ -29,12 +29,11 @@ type workGenerateResponse struct { @@ -29,12 +29,11 @@ type workGenerateResponse struct {
Work string `json:"work"`
}
func (b *block) sign(privateKey *big.Int) error {
publicKey := derivePublicKey(privateKey)
if err := b.addHashIfUnhashed(publicKey); err != nil {
func (b *block) sign(a account) error {
if err := b.addHashIfUnhashed(a); err != nil {
return err
}
signature, err := sign(privateKey, b.HashBytes)
signature, err := sign(a, b.HashBytes)
if err != nil {
return err
}
@ -42,23 +41,23 @@ func (b *block) sign(privateKey *big.Int) error { @@ -42,23 +41,23 @@ 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 {
func (b *block) verifySignature(a account) (err error) {
if err = b.addHashIfUnhashed(a); 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)) {
if !isValidSignature(a, b.HashBytes, bigIntToBytes(sig, 64)) {
err = errInvalidSignature
}
return
}
func (b *block) addHashIfUnhashed(publicKey *big.Int) error {
func (b *block) addHashIfUnhashed(a account) error {
if b.Hash == "" || len(b.HashBytes) == 0 {
hashBytes, err := b.hash(publicKey)
hashBytes, err := b.hash(a)
if err != nil {
return err
}
@ -68,14 +67,14 @@ func (b *block) addHashIfUnhashed(publicKey *big.Int) error { @@ -68,14 +67,14 @@ func (b *block) addHashIfUnhashed(publicKey *big.Int) error {
return nil
}
func (b *block) hash(publicKey *big.Int) ([]byte, error) {
func (b *block) hash(a account) ([]byte, error) {
// See https://nanoo.tools/block for a reference.
msg := make([]byte, 176, 176)
msg[31] = 0x6 // block preamble
copy(msg[32:64], bigIntToBytes(publicKey, 32))
copy(msg[32:64], bigIntToBytes(a.publicKey, 32))
previous, err := hex.DecodeString(b.Previous)
if err != nil {
@ -105,11 +104,10 @@ func (b *block) hash(publicKey *big.Int) ([]byte, error) { @@ -105,11 +104,10 @@ func (b *block) hash(publicKey *big.Int) ([]byte, error) {
return hash[:], nil
}
func (b *block) addWork(workThreshold uint64, privateKey *big.Int) error {
func (b *block) addWork(workThreshold uint64, a account) error {
var hash string
if b.Previous == "0000000000000000000000000000000000000000000000000000000000000000" {
publicKey := derivePublicKey(privateKey)
hash = fmt.Sprintf("%064X", bigIntToBytes(publicKey, 32))
hash = fmt.Sprintf("%064X", bigIntToBytes(a.publicKey, 32))
} else {
hash = b.Previous
}

20
change.go

@ -3,23 +3,21 @@ package main @@ -3,23 +3,21 @@ package main
import (
"flag"
"fmt"
"math/big"
"os"
)
func changeRepresentative() error {
seed, err := getSeed()
account, err := ownAccount()
if err != nil {
return err
}
privateKey := getPrivateKey(seed, uint32(accountIndexFlag))
info, err := getAccountInfo(privateKey)
info, err := account.getInfo()
if err != nil {
return err
}
representative := flag.Arg(1)
fmt.Fprintf(os.Stderr, "Creating change block... ")
err = changeRepresentativeOfAccount(info, representative, privateKey)
err = account.changeRepresentativeOfAccount(info, representative)
if err != nil {
fmt.Fprintln(os.Stderr, "")
return err
@ -28,23 +26,19 @@ func changeRepresentative() error { @@ -28,23 +26,19 @@ func changeRepresentative() error {
return nil
}
func changeRepresentativeOfAccount(info accountInfo, representative string, privateKey *big.Int) error {
address, err := getAddress(privateKey)
if err != nil {
return err
}
func (a account) changeRepresentativeOfAccount(info accountInfo, representative string) error {
block := block{
Type: "state",
Account: address,
Account: a.address,
Previous: info.Frontier,
Representative: representative,
Balance: info.Balance,
Link: "0000000000000000000000000000000000000000000000000000000000000000",
}
if err = block.sign(privateKey); err != nil {
if err := block.sign(a); err != nil {
return err
}
if err = block.addWork(changeWorkThreshold, privateKey); err != nil {
if err := block.addWork(changeWorkThreshold, a); err != nil {
return err
}
process := process{

13
ed25519.go

@ -1,23 +1,20 @@ @@ -1,23 +1,20 @@
package main
import (
"math/big"
"filippo.io/edwards25519"
"golang.org/x/crypto/blake2b"
)
func sign(privateKey *big.Int, msg []byte) ([]byte, error) {
func sign(a account, msg []byte) ([]byte, error) {
// This implementation based on the one from github.com/iotaledger/iota.go.
publicKey := derivePublicKey(privateKey)
signature := make([]byte, 64, 64)
h, err := blake2b.New512(nil)
if err != nil {
return signature, err
}
h.Write(bigIntToBytes(privateKey, 32))
h.Write(bigIntToBytes(a.privateKey, 32))
var digest1, messageDigest, hramDigest [64]byte
h.Sum(digest1[:0])
@ -36,7 +33,7 @@ func sign(privateKey *big.Int, msg []byte) ([]byte, error) { @@ -36,7 +33,7 @@ func sign(privateKey *big.Int, msg []byte) ([]byte, error) {
h.Reset()
h.Write(encodedR[:])
h.Write(bigIntToBytes(publicKey, 32))
h.Write(bigIntToBytes(a.publicKey, 32))
h.Write(msg)
h.Sum(hramDigest[:0])
@ -49,10 +46,10 @@ func sign(privateKey *big.Int, msg []byte) ([]byte, error) { @@ -49,10 +46,10 @@ func sign(privateKey *big.Int, msg []byte) ([]byte, error) {
return signature, nil
}
func isValidSignature(publicKey *big.Int, msg, sig []byte) bool {
func isValidSignature(a account, msg, sig []byte) bool {
// This implementation based on the one from github.com/iotaledger/iota.go.
publicKeyBytes := bigIntToBytes(publicKey, 32)
publicKeyBytes := bigIntToBytes(a.publicKey, 32)
// ZIP215: this works because SetBytes does not check that encodings are canonical
A, err := new(edwards25519.Point).SetBytes(publicKeyBytes)

32
send.go

@ -13,17 +13,19 @@ import ( @@ -13,17 +13,19 @@ import (
func sendFunds() error {
amount := flag.Arg(1)
recipient := flag.Arg(2)
seed, err := getSeedForSending(amount, recipient)
account, err := ownAccount()
if err != nil {
return err
}
privateKey := getPrivateKey(seed, uint32(accountIndexFlag))
info, err := getAccountInfo(privateKey)
if err = letUserVerifySend(amount, recipient); err != nil {
return err
}
info, err := account.getInfo()
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "Creating send block... ")
err = sendFundsToAccount(info, amount, recipient, privateKey)
err = account.sendFundsToAccount(info, amount, recipient)
if err != nil {
return err
}
@ -31,11 +33,7 @@ func sendFunds() error { @@ -31,11 +33,7 @@ func sendFunds() error {
return nil
}
func getSeedForSending(amount, recipient string) (*big.Int, error) {
seed, err := getSeed()
if err != nil {
return nil, err
}
func letUserVerifySend(amount, recipient string) (err error) {
if !yFlag {
fmt.Printf("Send %s NANO to %s? [y/N]: ", amount, recipient)
@ -49,7 +47,7 @@ func getSeedForSending(amount, recipient string) (*big.Int, error) { @@ -49,7 +47,7 @@ func getSeedForSending(amount, recipient string) (*big.Int, error) {
}
if err != nil {
msg := "could not open terminal for confirmation input: %v"
return nil, fmt.Errorf(msg, err)
return fmt.Errorf(msg, err)
}
defer tty.Close()
@ -60,14 +58,10 @@ func getSeedForSending(amount, recipient string) (*big.Int, error) { @@ -60,14 +58,10 @@ func getSeedForSending(amount, recipient string) (*big.Int, error) {
os.Exit(0)
}
}
return seed, nil
return
}
func sendFundsToAccount(info accountInfo, amount, recipient string, privateKey *big.Int) error {
address, err := getAddress(privateKey)
if err != nil {
return err
}
func (a account) sendFundsToAccount(info accountInfo, amount, recipient string) error {
balance, err := getBalanceAfterSend(info.Balance, amount)
if err != nil {
return err
@ -79,16 +73,16 @@ func sendFundsToAccount(info accountInfo, amount, recipient string, privateKey * @@ -79,16 +73,16 @@ func sendFundsToAccount(info accountInfo, amount, recipient string, privateKey *
recipientBytes := bigIntToBytes(recipientNumber, 32)
block := block{
Type: "state",
Account: address,
Account: a.address,
Previous: info.Frontier,
Representative: info.Representative,
Balance: balance.String(),
Link: fmt.Sprintf("%064X", recipientBytes),
}
if err = block.sign(privateKey); err != nil {
if err = block.sign(a); err != nil {
return err
}
if err = block.addWork(sendWorkThreshold, privateKey); err != nil {
if err = block.addWork(sendWorkThreshold, a); err != nil {
return err
}
process := process{

17
util.go

@ -1,30 +1,13 @@ @@ -1,30 +1,13 @@
package main
import (
"bufio"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"os"
"strings"
)
// getSeed takes the first line of the standard input and interprets it
// as a hexadecimal representation of a 32byte seed.
func getSeed() (*big.Int, error) {
in := bufio.NewReader(os.Stdin)
firstLine, err := in.ReadString('\n')
if err != nil {
return nil, err
}
seed, ok := big.NewInt(0).SetString(strings.TrimSpace(firstLine), 16)
if !ok {
return nil, fmt.Errorf("could not parse seed")
}
return seed, nil
}
func base32Encode(in *big.Int) string {
alphabet := []byte("13456789abcdefghijkmnopqrstuwxyz")
bigZero := big.NewInt(0)

Loading…
Cancel
Save