mirror of https://github.com/codesoap/atto.git
mirror of https://github.com/codesoap/atto
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.
197 lines
4.6 KiB
197 lines
4.6 KiB
1 year ago
|
package main
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"encoding/hex"
|
||
|
"fmt"
|
||
|
"math/big"
|
||
|
|
||
|
"filippo.io/edwards25519"
|
||
|
"golang.org/x/crypto/blake2b"
|
||
|
)
|
||
|
|
||
|
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"`
|
||
|
Hash string `json:"-"`
|
||
|
}
|
||
|
|
||
|
type workerResult struct {
|
||
|
hashNumber uint64
|
||
|
nonce uint64
|
||
|
}
|
||
|
|
||
|
func (b *block) sign(privateKey *big.Int) error {
|
||
|
// Look at https://nanoo.tools/block for a reference. This
|
||
|
// implementation based on the one from github.com/iotaledger/iota.go.
|
||
|
|
||
|
publicKey := derivePublicKey(privateKey)
|
||
|
hash, err := b.hash(publicKey)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
b.Hash = fmt.Sprintf("%064X", hash)
|
||
|
signature := make([]byte, 64, 64)
|
||
|
|
||
|
privateKeyBytes := make([]byte, 32, 32)
|
||
|
privateKey.FillBytes(privateKeyBytes)
|
||
|
h, err := blake2b.New512(nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
h.Write(privateKeyBytes)
|
||
|
|
||
|
var digest1, messageDigest, hramDigest [64]byte
|
||
|
h.Sum(digest1[:0])
|
||
|
|
||
|
s := new(edwards25519.Scalar).SetBytesWithClamping(digest1[:32])
|
||
|
|
||
|
h.Reset()
|
||
|
h.Write(digest1[32:])
|
||
|
h.Write(hash)
|
||
|
h.Sum(messageDigest[:0])
|
||
|
|
||
|
rReduced := new(edwards25519.Scalar).SetUniformBytes(messageDigest[:])
|
||
|
R := new(edwards25519.Point).ScalarBaseMult(rReduced)
|
||
|
|
||
|
encodedR := R.Bytes()
|
||
|
|
||
|
h.Reset()
|
||
|
h.Write(encodedR[:])
|
||
|
publicKeyBytes := make([]byte, 32, 32)
|
||
|
publicKey.FillBytes(publicKeyBytes)
|
||
|
h.Write(publicKeyBytes)
|
||
|
h.Write(hash)
|
||
|
h.Sum(hramDigest[:0])
|
||
|
|
||
|
kReduced := new(edwards25519.Scalar).SetUniformBytes(hramDigest[:])
|
||
|
S := new(edwards25519.Scalar).MultiplyAdd(kReduced, s, rReduced)
|
||
|
|
||
|
copy(signature[:], encodedR[:])
|
||
|
copy(signature[32:], S.Bytes())
|
||
|
|
||
|
b.Signature = fmt.Sprintf("%0128X", signature)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (b *block) hash(publicKey *big.Int) ([]byte, error) {
|
||
|
msg := make([]byte, 176, 176)
|
||
|
|
||
|
msg[31] = 0x6 // block preamble
|
||
|
|
||
|
publicKeyBytes := make([]byte, 32, 32)
|
||
|
publicKey.FillBytes(publicKeyBytes)
|
||
|
copy(msg[32:64], publicKeyBytes)
|
||
|
|
||
|
previous, err := hex.DecodeString(b.Previous)
|
||
|
if err != nil {
|
||
|
return []byte{}, err
|
||
|
}
|
||
|
copy(msg[64:96], previous)
|
||
|
|
||
|
representative, err := getPublicKeyFromAddress(b.Representative)
|
||
|
representativeBytes := make([]byte, 32, 32)
|
||
|
representative.FillBytes(representativeBytes)
|
||
|
if err != nil {
|
||
|
return []byte{}, err
|
||
|
}
|
||
|
copy(msg[96:128], representativeBytes)
|
||
|
|
||
|
balance, ok := big.NewInt(0).SetString(b.Balance, 10)
|
||
|
if !ok {
|
||
|
return []byte{}, fmt.Errorf("cannot parse '%s' as an integer", b.Balance)
|
||
|
}
|
||
|
balanceBytes := make([]byte, 16, 16)
|
||
|
balance.FillBytes(balanceBytes)
|
||
|
copy(msg[128:144], balanceBytes)
|
||
|
|
||
|
link, err := hex.DecodeString(b.Link)
|
||
|
if err != nil {
|
||
|
return []byte{}, err
|
||
|
}
|
||
|
copy(msg[144:176], link)
|
||
|
|
||
|
hash := blake2b.Sum256(msg)
|
||
|
return hash[:], nil
|
||
|
}
|
||
|
|
||
|
func (b *block) addWork(workThreshold uint64, privateKey *big.Int) (err error) {
|
||
|
var suffix []byte
|
||
|
if b.Previous == "0000000000000000000000000000000000000000000000000000000000000000" {
|
||
|
publicKey := derivePublicKey(privateKey)
|
||
|
suffix = make([]byte, 32, 32)
|
||
|
publicKey.FillBytes(suffix)
|
||
|
} else {
|
||
|
suffix, err = hex.DecodeString(b.Previous)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
nonce, err := findNonce(workThreshold, suffix)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
b.Work = fmt.Sprintf("%016x", nonce)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func findNonce(workThreshold uint64, suffix []byte) (uint64, error) {
|
||
|
results := make(chan workerResult)
|
||
|
quit := make(chan bool, workerRoutines)
|
||
|
errs := make(chan error, workerRoutines)
|
||
|
for i := 0; i < workerRoutines; i++ {
|
||
|
go calculateHashes(suffix, uint64(i), results, quit, errs)
|
||
|
}
|
||
|
for {
|
||
|
select {
|
||
|
case result := <-results:
|
||
|
if result.hashNumber >= workThreshold {
|
||
|
for i := 0; i < workerRoutines; i++ {
|
||
|
quit <- true
|
||
|
}
|
||
|
return result.nonce, nil
|
||
|
}
|
||
|
case err := <-errs:
|
||
|
for i := 0; i < workerRoutines; i++ {
|
||
|
quit <- true
|
||
|
}
|
||
|
return 0, err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func calculateHashes(suffix []byte, nonce uint64, results chan workerResult, quit chan bool, errs chan error) {
|
||
|
nonceBytes := make([]byte, 8)
|
||
|
hasher, err := blake2b.New(8, nil)
|
||
|
if err != nil {
|
||
|
errs <- err
|
||
|
return
|
||
|
}
|
||
|
for {
|
||
|
select {
|
||
|
case <-quit:
|
||
|
return
|
||
|
default:
|
||
|
binary.LittleEndian.PutUint64(nonceBytes, nonce)
|
||
|
_, err := hasher.Write(append(nonceBytes, suffix...))
|
||
|
if err != nil {
|
||
|
errs <- err
|
||
|
return
|
||
|
}
|
||
|
hashBytes := hasher.Sum(nil)
|
||
|
results <- workerResult{
|
||
|
hashNumber: binary.LittleEndian.Uint64(hashBytes),
|
||
|
nonce: nonce,
|
||
|
}
|
||
|
hasher.Reset()
|
||
|
nonce += uint64(workerRoutines)
|
||
|
}
|
||
|
}
|
||
|
}
|