forked from TrueCloudLab/neoneo-go
5c2ea2d5bb
We need to provide magic for both main and fallback transactions during decoding, because transactions hashes depend on it.
174 lines
5.4 KiB
Go
174 lines
5.4 KiB
Go
package payload
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
)
|
|
|
|
// P2PNotaryRequest contains main and fallback transactions for the Notary service.
|
|
type P2PNotaryRequest struct {
|
|
MainTransaction *transaction.Transaction
|
|
FallbackTransaction *transaction.Transaction
|
|
Network netmode.Magic
|
|
|
|
Witness transaction.Witness
|
|
|
|
hash util.Uint256
|
|
signedHash util.Uint256
|
|
}
|
|
|
|
// NewP2PNotaryRequestFromBytes decodes P2PNotaryRequest from the given bytes.
|
|
func NewP2PNotaryRequestFromBytes(network netmode.Magic, b []byte) (*P2PNotaryRequest, error) {
|
|
req := &P2PNotaryRequest{Network: network}
|
|
br := io.NewBinReaderFromBuf(b)
|
|
req.DecodeBinary(br)
|
|
if br.Err != nil {
|
|
return nil, br.Err
|
|
}
|
|
_ = br.ReadB()
|
|
if br.Err == nil {
|
|
return nil, errors.New("additional data after the payload")
|
|
}
|
|
return req, nil
|
|
}
|
|
|
|
// Bytes returns serialized P2PNotaryRequest payload.
|
|
func (r *P2PNotaryRequest) Bytes() ([]byte, error) {
|
|
buf := io.NewBufBinWriter()
|
|
r.EncodeBinary(buf.BinWriter)
|
|
if buf.Err != nil {
|
|
return nil, buf.Err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// Hash returns payload's hash.
|
|
func (r *P2PNotaryRequest) Hash() util.Uint256 {
|
|
if r.hash.Equals(util.Uint256{}) {
|
|
if r.createHash() != nil {
|
|
panic("failed to compute hash!")
|
|
}
|
|
}
|
|
return r.hash
|
|
}
|
|
|
|
// GetSignedHash returns a hash of the payload used to verify it.
|
|
func (r *P2PNotaryRequest) GetSignedHash() util.Uint256 {
|
|
if r.signedHash.Equals(util.Uint256{}) {
|
|
if r.createHash() != nil {
|
|
panic("failed to compute hash!")
|
|
}
|
|
}
|
|
return r.signedHash
|
|
}
|
|
|
|
// GetSignedPart returns a part of the payload which must be signed.
|
|
func (r *P2PNotaryRequest) GetSignedPart() []byte {
|
|
buf := io.NewBufBinWriter()
|
|
buf.WriteU32LE(uint32(r.Network))
|
|
r.encodeHashableFields(buf.BinWriter)
|
|
if buf.Err != nil {
|
|
return nil
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// createHash creates hash of the payload.
|
|
func (r *P2PNotaryRequest) createHash() error {
|
|
b := r.GetSignedPart()
|
|
if b == nil {
|
|
return errors.New("failed to serialize hashable data")
|
|
}
|
|
r.updateHashes(b)
|
|
return nil
|
|
}
|
|
|
|
// updateHashes updates Payload's hashes based on the given buffer which should
|
|
// be a signable data slice.
|
|
func (r *P2PNotaryRequest) updateHashes(b []byte) {
|
|
r.signedHash = hash.Sha256(b)
|
|
r.hash = hash.Sha256(r.signedHash.BytesBE())
|
|
}
|
|
|
|
// DecodeBinaryUnsigned reads payload from w excluding signature.
|
|
func (r *P2PNotaryRequest) decodeHashableFields(br *io.BinReader) {
|
|
r.MainTransaction = &transaction.Transaction{Network: r.Network}
|
|
r.FallbackTransaction = &transaction.Transaction{Network: r.Network}
|
|
r.MainTransaction.DecodeBinary(br)
|
|
r.FallbackTransaction.DecodeBinary(br)
|
|
if br.Err == nil {
|
|
br.Err = r.isValid()
|
|
}
|
|
if br.Err == nil {
|
|
br.Err = r.createHash()
|
|
}
|
|
}
|
|
|
|
// DecodeBinary implements io.Serializable interface.
|
|
func (r *P2PNotaryRequest) DecodeBinary(br *io.BinReader) {
|
|
r.decodeHashableFields(br)
|
|
if br.Err == nil {
|
|
r.Witness.DecodeBinary(br)
|
|
}
|
|
}
|
|
|
|
// encodeHashableFields writes payload to w excluding signature.
|
|
func (r *P2PNotaryRequest) encodeHashableFields(bw *io.BinWriter) {
|
|
r.MainTransaction.EncodeBinary(bw)
|
|
r.FallbackTransaction.EncodeBinary(bw)
|
|
}
|
|
|
|
// EncodeBinary implements Serializable interface.
|
|
func (r *P2PNotaryRequest) EncodeBinary(bw *io.BinWriter) {
|
|
r.encodeHashableFields(bw)
|
|
r.Witness.EncodeBinary(bw)
|
|
}
|
|
|
|
func (r *P2PNotaryRequest) isValid() error {
|
|
nKeysMain := r.MainTransaction.GetAttributes(transaction.NotaryAssistedT)
|
|
if len(nKeysMain) == 0 {
|
|
return errors.New("main transaction should have NotaryAssisted attribute")
|
|
}
|
|
if nKeysMain[0].Value.(*transaction.NotaryAssisted).NKeys == 0 {
|
|
return errors.New("main transaction should have NKeys > 0")
|
|
}
|
|
if len(r.FallbackTransaction.Signers) != 2 {
|
|
return errors.New("fallback transaction should have two signers")
|
|
}
|
|
if len(r.FallbackTransaction.Scripts) != 2 {
|
|
return errors.New("fallback transaction should have dummy Notary witness and valid witness for the second signer")
|
|
}
|
|
if len(r.FallbackTransaction.Scripts[0].InvocationScript) != 66 ||
|
|
len(r.FallbackTransaction.Scripts[0].VerificationScript) != 0 ||
|
|
!bytes.HasPrefix(r.FallbackTransaction.Scripts[0].InvocationScript, []byte{byte(opcode.PUSHDATA1), 64}) {
|
|
return errors.New("fallback transaction has invalid dummy Notary witness")
|
|
}
|
|
if !r.FallbackTransaction.HasAttribute(transaction.NotValidBeforeT) {
|
|
return errors.New("fallback transactions should have NotValidBefore attribute")
|
|
}
|
|
conflicts := r.FallbackTransaction.GetAttributes(transaction.ConflictsT)
|
|
if len(conflicts) != 1 {
|
|
return errors.New("fallback transaction should have one Conflicts attribute")
|
|
}
|
|
if conflicts[0].Value.(*transaction.Conflicts).Hash != r.MainTransaction.Hash() {
|
|
return errors.New("fallback transaction does not conflicts with the main transaction")
|
|
}
|
|
nKeysFallback := r.FallbackTransaction.GetAttributes(transaction.NotaryAssistedT)
|
|
if len(nKeysFallback) == 0 {
|
|
return errors.New("fallback transaction should have NotaryAssisted attribute")
|
|
}
|
|
if nKeysFallback[0].Value.(*transaction.NotaryAssisted).NKeys != 0 {
|
|
return errors.New("fallback transaction should have NKeys = 0")
|
|
}
|
|
if r.MainTransaction.ValidUntilBlock != r.FallbackTransaction.ValidUntilBlock {
|
|
return errors.New("both main and fallback transactions should have the same ValidUntil value")
|
|
}
|
|
return nil
|
|
}
|