You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
5.1 KiB

package atto
1 year ago
import (
"encoding/hex"
"encoding/json"
1 year ago
"fmt"
"math/big"
"golang.org/x/crypto/blake2b"
)
var errInvalidSignature = fmt.Errorf("invalid block signature")
// ErrSignatureMissing is used when the Signature of a Block is missing
// but required for the attempted operation.
var ErrSignatureMissing = fmt.Errorf("signature is missing")
// ErrWorkMissing is used when the Work of a Block is missing but
// required for the attempted operation.
var ErrWorkMissing = fmt.Errorf("work is missing")
// BlockSubType represents the sub-type of a block.
type BlockSubType int64
const (
// SubTypeReceive denotes blocks which raise the balance.
SubTypeReceive BlockSubType = iota
// SubTypeChange denotes blocks which change the representative.
SubTypeChange
// SubTypeSend denotes blocks which lower the balance.
SubTypeSend
)
// Block represents a block in the block chain of an account.
type Block struct {
Type string `json:"type"`
Account string `json:"account"`
Previous string `json:"previous"`
Representative string `json:"representative"`
Balance string `json:"balance"`
Link string `json:"link"`
Signature string `json:"signature"`
Work string `json:"work"`
// This field is not part of the JSON but needed to improve the
// performance of FetchWork and the security of Submit.
SubType BlockSubType `json:"-"`
1 year ago
}
type workGenerateResponse struct {
Error string `json:"error"`
Work string `json:"work"`
1 year ago
}
// Sign computes and sets the Signature of b.
func (b *Block) Sign(privateKey *big.Int) error {
publicKey, err := getPublicKeyFromAddress(b.Account)
if err != nil {
return err
}
hash, err := b.hashBytes()
if err != nil {
return err
}
signature, err := sign(publicKey, privateKey, hash)
1 year ago
if err != nil {
return err
}
b.Signature = fmt.Sprintf("%0128X", signature)
return nil
}
func (b *Block) verifySignature(a Account) (err error) {
sig, ok := big.NewInt(0).SetString(b.Signature, 16)
if !ok {
return fmt.Errorf("cannot parse '%s' as an integer", b.Signature)
}
hash, err := b.hashBytes()
if err != nil {
return err
}
if !isValidSignature(a.PublicKey, hash, bigIntToBytes(sig, 64)) {
err = errInvalidSignature
}
return
}
// FetchWork uses the generate_work RPC on node to fetch and then set
// the Work of b.
func (b *Block) FetchWork(node string) error {
var hash string
if b.Previous == "0000000000000000000000000000000000000000000000000000000000000000" {
publicKey, err := getPublicKeyFromAddress(b.Account)
if err != nil {
return err
}
hash = fmt.Sprintf("%064X", bigIntToBytes(publicKey, 32))
} else {
hash = b.Previous
}
requestBody := fmt.Sprintf(`{"action":"work_generate", "hash":"%s"`, string(hash))
if b.SubType == SubTypeReceive {
// Receive blocks need less work, so lower the difficulty.
var receiveWorkThreshold uint64 = 0xfffffe0000000000
requestBody += fmt.Sprintf(`, "difficulty":"%016x"`, receiveWorkThreshold)
}
requestBody += `}`
responseBytes, err := doRPC(requestBody, node)
if err != nil {
return err
}
var response workGenerateResponse
if err = json.Unmarshal(responseBytes, &response); err != nil {
return err
}
// Need to check response.Error because of
// https://github.com/nanocurrency/nano-node/issues/1782.
if response.Error != "" {
return fmt.Errorf("could not get work for block: %s", response.Error)
}
b.Work = response.Work
return nil
}
// Hash calculates the block's hash and returns it's string
// representation.
func (b Block) Hash() (string, error) {
hashBytes, err := b.hashBytes()
if err != nil {
return "", err
}
return fmt.Sprintf("%064X", hashBytes), nil
}
func (b Block) hashBytes() ([]byte, error) {
// See https://nanoo.tools/block for a reference.
1 year ago
msg := make([]byte, 176, 176)
msg[31] = 0x6 // block preamble
publicKey, err := getPublicKeyFromAddress(b.Account)
if err != nil {
return nil, err
}
copy(msg[32:64], bigIntToBytes(publicKey, 32))
1 year ago
previous, err := hex.DecodeString(b.Previous)
if err != nil {
return nil, err
1 year ago
}
copy(msg[64:96], previous)
representative, err := getPublicKeyFromAddress(b.Representative)
if err != nil {
return nil, err
1 year ago
}
copy(msg[96:128], bigIntToBytes(representative, 32))
1 year ago
balance, ok := big.NewInt(0).SetString(b.Balance, 10)
if !ok {
return nil, fmt.Errorf("cannot parse '%s' as an integer", b.Balance)
1 year ago
}
copy(msg[128:144], bigIntToBytes(balance, 16))
1 year ago
link, err := hex.DecodeString(b.Link)
if err != nil {
return nil, err
1 year ago
}
copy(msg[144:176], link)
hash := blake2b.Sum256(msg)
return hash[:], nil
1 year ago
}
// Submit submits the Block to the given node. Work and Signature of b
// must be populated beforehand.
func (b Block) Submit(node string) error {
if b.Work == "" {
return ErrWorkMissing
1 year ago
}
if b.Signature == "" {
return ErrSignatureMissing
1 year ago
}
var subType string
switch b.SubType {
case SubTypeReceive:
subType = "receive"
case SubTypeChange:
subType = "change"
case SubTypeSend:
subType = "send"
}
process := process{
Action: "process",
JsonBlock: "true",
SubType: subType,
Block: b,
1 year ago
}
return doProcessRPC(process, node)
1 year ago
}