From 1ae05c371018db7b66313cb0ce0891aad0090aa3 Mon Sep 17 00:00:00 2001 From: Alan Orwick Date: Thu, 28 Sep 2023 15:11:26 -0500 Subject: [PATCH] wip: ristretto transaction signing --- core/types/external_tx.go | 54 +-- core/types/internal_to_external_tx.go | 73 ++--- core/types/internal_tx.go | 71 ++-- core/types/transaction.go | 33 +- core/types/transaction_marshalling.go | 59 +--- core/types/transaction_signing.go | 37 ++- core/types/transaction_signing_test.go | 108 ++++++ crypto/keypair.go | 101 ++++++ crypto/sr25519/sr25519_signing.go | 434 +++++++++++++++++++++++++ crypto/sr25519/sr25519_signing_test.go | 182 +++++++++++ go.mod | 9 +- go.sum | 32 ++ 12 files changed, 1023 insertions(+), 170 deletions(-) create mode 100644 core/types/transaction_signing_test.go create mode 100644 crypto/keypair.go create mode 100644 crypto/sr25519/sr25519_signing.go create mode 100644 crypto/sr25519/sr25519_signing_test.go diff --git a/core/types/external_tx.go b/core/types/external_tx.go index f51f32ce1a..edc232f031 100644 --- a/core/types/external_tx.go +++ b/core/types/external_tx.go @@ -4,6 +4,7 @@ import ( "math/big" "github.com/dominant-strategies/go-quai/common" + "github.com/dominant-strategies/go-quai/crypto/sr25519" ) type ExternalTx struct { @@ -13,6 +14,7 @@ type ExternalTx struct { GasFeeCap *big.Int Gas uint64 To *common.Address `rlp:"nilString"` // nil means contract creation + FromPubKey sr25519.PublicKey Value *big.Int Data []byte AccessList AccessList @@ -64,11 +66,12 @@ func (p *PendingEtxs) IsValid(hasher TrieHasher) bool { // copy creates a deep copy of the transaction data and initializes all fields. func (tx *ExternalTx) copy() TxData { cpy := &ExternalTx{ - Nonce: tx.Nonce, - To: tx.To, // TODO: copy pointed-to address - Data: common.CopyBytes(tx.Data), - Gas: tx.Gas, - Sender: tx.Sender, + Nonce: tx.Nonce, + To: tx.To, // TODO: copy pointed-to address + FromPubKey: tx.FromPubKey, + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + Sender: tx.Sender, // These are copied below. AccessList: make(AccessList, len(tx.AccessList)), @@ -94,29 +97,30 @@ func (tx *ExternalTx) copy() TxData { } // accessors for innerTx. -func (tx *ExternalTx) txType() byte { return ExternalTxType } -func (tx *ExternalTx) chainID() *big.Int { return tx.ChainID } -func (tx *ExternalTx) protected() bool { return true } -func (tx *ExternalTx) accessList() AccessList { return tx.AccessList } -func (tx *ExternalTx) data() []byte { return tx.Data } -func (tx *ExternalTx) gas() uint64 { return tx.Gas } -func (tx *ExternalTx) gasFeeCap() *big.Int { return tx.GasFeeCap } -func (tx *ExternalTx) gasTipCap() *big.Int { return tx.GasTipCap } -func (tx *ExternalTx) gasPrice() *big.Int { return tx.GasFeeCap } -func (tx *ExternalTx) value() *big.Int { return tx.Value } -func (tx *ExternalTx) nonce() uint64 { return tx.Nonce } -func (tx *ExternalTx) to() *common.Address { return tx.To } -func (tx *ExternalTx) etxGasLimit() uint64 { panic("external TX does not have etxGasLimit") } -func (tx *ExternalTx) etxGasPrice() *big.Int { panic("external TX does not have etxGasPrice") } -func (tx *ExternalTx) etxGasTip() *big.Int { panic("external TX does not have etxGasTip") } -func (tx *ExternalTx) etxData() []byte { panic("external TX does not have etxData") } -func (tx *ExternalTx) etxAccessList() AccessList { panic("external TX does not have etxAccessList") } +func (tx *ExternalTx) txType() byte { return ExternalTxType } +func (tx *ExternalTx) chainID() *big.Int { return tx.ChainID } +func (tx *ExternalTx) protected() bool { return true } +func (tx *ExternalTx) accessList() AccessList { return tx.AccessList } +func (tx *ExternalTx) data() []byte { return tx.Data } +func (tx *ExternalTx) gas() uint64 { return tx.Gas } +func (tx *ExternalTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *ExternalTx) gasTipCap() *big.Int { return tx.GasTipCap } +func (tx *ExternalTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *ExternalTx) value() *big.Int { return tx.Value } +func (tx *ExternalTx) nonce() uint64 { return tx.Nonce } +func (tx *ExternalTx) to() *common.Address { return tx.To } +func (tx *ExternalTx) fromPubKey() sr25519.PublicKey { return tx.FromPubKey } +func (tx *ExternalTx) etxGasLimit() uint64 { panic("external TX does not have etxGasLimit") } +func (tx *ExternalTx) etxGasPrice() *big.Int { panic("external TX does not have etxGasPrice") } +func (tx *ExternalTx) etxGasTip() *big.Int { panic("external TX does not have etxGasTip") } +func (tx *ExternalTx) etxData() []byte { panic("external TX does not have etxData") } +func (tx *ExternalTx) etxAccessList() AccessList { panic("external TX does not have etxAccessList") } -func (tx *ExternalTx) rawSignatureValues() (v, r, s *big.Int) { +func (tx *ExternalTx) rawSignatureValues() []byte { // Signature values are ignored for external transactions - return nil, nil, nil + return nil } -func (tx *ExternalTx) setSignatureValues(chainID, v, r, s *big.Int) { +func (tx *ExternalTx) setSignatureValues(chainID *big.Int, sig []byte) { // Signature values are ignored for external transactions } diff --git a/core/types/internal_to_external_tx.go b/core/types/internal_to_external_tx.go index 321ddcee07..7c365f6297 100644 --- a/core/types/internal_to_external_tx.go +++ b/core/types/internal_to_external_tx.go @@ -20,6 +20,7 @@ import ( "math/big" "github.com/dominant-strategies/go-quai/common" + "github.com/dominant-strategies/go-quai/crypto/sr25519" ) type InternalToExternalTx struct { @@ -28,7 +29,8 @@ type InternalToExternalTx struct { GasTipCap *big.Int GasFeeCap *big.Int Gas uint64 - To *common.Address `rlp:"nilString"` // nil means contract creation + To *common.Address `rlp:"nilString"` // nil means contract creation + FromPubKey sr25519.PublicKey `rlp:"nilString"` Value *big.Int Data []byte // this probably is not applicable AccessList AccessList // this probably is not applicable @@ -40,18 +42,17 @@ type InternalToExternalTx struct { ETXAccessList AccessList // Signature values - V *big.Int `json:"v" gencodec:"required"` - R *big.Int `json:"r" gencodec:"required"` - S *big.Int `json:"s" gencodec:"required"` + Signature []byte } // copy creates a deep copy of the transaction data and initializes all fields. func (tx *InternalToExternalTx) copy() TxData { cpy := &InternalToExternalTx{ - Nonce: tx.Nonce, - To: tx.To, // TODO: copy pointed-to address - Data: common.CopyBytes(tx.Data), - Gas: tx.Gas, + Nonce: tx.Nonce, + To: tx.To, // TODO: copy pointed-to address + FromPubKey: tx.FromPubKey, + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, // These are copied below. AccessList: make(AccessList, len(tx.AccessList)), Value: new(big.Int), @@ -63,9 +64,7 @@ func (tx *InternalToExternalTx) copy() TxData { ETXGasTip: new(big.Int), ETXData: common.CopyBytes(tx.ETXData), ETXAccessList: make(AccessList, len(tx.ETXAccessList)), - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), + Signature: common.CopyBytes(tx.Signature), } copy(cpy.AccessList, tx.AccessList) copy(cpy.ETXAccessList, tx.ETXAccessList) @@ -87,41 +86,33 @@ func (tx *InternalToExternalTx) copy() TxData { if tx.ETXGasTip != nil { cpy.ETXGasTip.Set(tx.ETXGasTip) } - if tx.V != nil { - cpy.V.Set(tx.V) - } - if tx.R != nil { - cpy.R.Set(tx.R) - } - if tx.S != nil { - cpy.S.Set(tx.S) - } return cpy } // accessors for innerTx. -func (tx *InternalToExternalTx) txType() byte { return InternalToExternalTxType } -func (tx *InternalToExternalTx) chainID() *big.Int { return tx.ChainID } -func (tx *InternalToExternalTx) protected() bool { return true } -func (tx *InternalToExternalTx) accessList() AccessList { return tx.AccessList } -func (tx *InternalToExternalTx) data() []byte { return tx.Data } -func (tx *InternalToExternalTx) gas() uint64 { return tx.Gas } -func (tx *InternalToExternalTx) gasFeeCap() *big.Int { return tx.GasFeeCap } -func (tx *InternalToExternalTx) gasTipCap() *big.Int { return tx.GasTipCap } -func (tx *InternalToExternalTx) gasPrice() *big.Int { return tx.GasFeeCap } -func (tx *InternalToExternalTx) value() *big.Int { return tx.Value } -func (tx *InternalToExternalTx) nonce() uint64 { return tx.Nonce } -func (tx *InternalToExternalTx) to() *common.Address { return tx.To } -func (tx *InternalToExternalTx) etxGasLimit() uint64 { return tx.ETXGasLimit } -func (tx *InternalToExternalTx) etxGasPrice() *big.Int { return tx.ETXGasPrice } -func (tx *InternalToExternalTx) etxGasTip() *big.Int { return tx.ETXGasTip } -func (tx *InternalToExternalTx) etxData() []byte { return tx.ETXData } -func (tx *InternalToExternalTx) etxAccessList() AccessList { return tx.ETXAccessList } +func (tx *InternalToExternalTx) txType() byte { return InternalToExternalTxType } +func (tx *InternalToExternalTx) chainID() *big.Int { return tx.ChainID } +func (tx *InternalToExternalTx) protected() bool { return true } +func (tx *InternalToExternalTx) accessList() AccessList { return tx.AccessList } +func (tx *InternalToExternalTx) data() []byte { return tx.Data } +func (tx *InternalToExternalTx) gas() uint64 { return tx.Gas } +func (tx *InternalToExternalTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *InternalToExternalTx) gasTipCap() *big.Int { return tx.GasTipCap } +func (tx *InternalToExternalTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *InternalToExternalTx) value() *big.Int { return tx.Value } +func (tx *InternalToExternalTx) nonce() uint64 { return tx.Nonce } +func (tx *InternalToExternalTx) to() *common.Address { return tx.To } +func (tx *InternalToExternalTx) fromPubKey() sr25519.PublicKey { return tx.FromPubKey } +func (tx *InternalToExternalTx) etxGasLimit() uint64 { return tx.ETXGasLimit } +func (tx *InternalToExternalTx) etxGasPrice() *big.Int { return tx.ETXGasPrice } +func (tx *InternalToExternalTx) etxGasTip() *big.Int { return tx.ETXGasTip } +func (tx *InternalToExternalTx) etxData() []byte { return tx.ETXData } +func (tx *InternalToExternalTx) etxAccessList() AccessList { return tx.ETXAccessList } -func (tx *InternalToExternalTx) rawSignatureValues() (v, r, s *big.Int) { - return tx.V, tx.R, tx.S +func (tx *InternalToExternalTx) rawSignatureValues() []byte { + return tx.Signature } -func (tx *InternalToExternalTx) setSignatureValues(chainID, v, r, s *big.Int) { - tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +func (tx *InternalToExternalTx) setSignatureValues(chainID *big.Int, sig []byte) { + tx.ChainID, tx.Signature = chainID, sig } diff --git a/core/types/internal_tx.go b/core/types/internal_tx.go index 2a14df68ba..255df7b213 100644 --- a/core/types/internal_tx.go +++ b/core/types/internal_tx.go @@ -20,6 +20,7 @@ import ( "math/big" "github.com/dominant-strategies/go-quai/common" + "github.com/dominant-strategies/go-quai/crypto/sr25519" ) type InternalTx struct { @@ -29,32 +30,30 @@ type InternalTx struct { GasFeeCap *big.Int Gas uint64 To *common.Address `rlp:"nilString"` // nil means contract creation + FromPubKey sr25519.PublicKey Value *big.Int Data []byte AccessList AccessList // Signature values - V *big.Int `json:"v" gencodec:"required"` - R *big.Int `json:"r" gencodec:"required"` - S *big.Int `json:"s" gencodec:"required"` + Signature []byte } // copy creates a deep copy of the transaction data and initializes all fields. func (tx *InternalTx) copy() TxData { cpy := &InternalTx{ - Nonce: tx.Nonce, - To: tx.To, // TODO: copy pointed-to address - Data: common.CopyBytes(tx.Data), - Gas: tx.Gas, + Nonce: tx.Nonce, + To: tx.To, // TODO: copy pointed-to address + FromPubKey: tx.FromPubKey, + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, // These are copied below. AccessList: make(AccessList, len(tx.AccessList)), Value: new(big.Int), ChainID: new(big.Int), GasTipCap: new(big.Int), GasFeeCap: new(big.Int), - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), + Signature: common.CopyBytes(tx.Signature), } copy(cpy.AccessList, tx.AccessList) if tx.Value != nil { @@ -69,41 +68,33 @@ func (tx *InternalTx) copy() TxData { if tx.GasFeeCap != nil { cpy.GasFeeCap.Set(tx.GasFeeCap) } - if tx.V != nil { - cpy.V.Set(tx.V) - } - if tx.R != nil { - cpy.R.Set(tx.R) - } - if tx.S != nil { - cpy.S.Set(tx.S) - } return cpy } // accessors for innerTx. -func (tx *InternalTx) txType() byte { return InternalTxType } -func (tx *InternalTx) chainID() *big.Int { return tx.ChainID } -func (tx *InternalTx) protected() bool { return true } -func (tx *InternalTx) accessList() AccessList { return tx.AccessList } -func (tx *InternalTx) data() []byte { return tx.Data } -func (tx *InternalTx) gas() uint64 { return tx.Gas } -func (tx *InternalTx) gasFeeCap() *big.Int { return tx.GasFeeCap } -func (tx *InternalTx) gasTipCap() *big.Int { return tx.GasTipCap } -func (tx *InternalTx) gasPrice() *big.Int { return tx.GasFeeCap } -func (tx *InternalTx) value() *big.Int { return tx.Value } -func (tx *InternalTx) nonce() uint64 { return tx.Nonce } -func (tx *InternalTx) to() *common.Address { return tx.To } -func (tx *InternalTx) etxGasLimit() uint64 { panic("internal TX does not have etxGasLimit") } -func (tx *InternalTx) etxGasPrice() *big.Int { panic("internal TX does not have etxGasPrice") } -func (tx *InternalTx) etxGasTip() *big.Int { panic("internal TX does not have etxGasTip") } -func (tx *InternalTx) etxData() []byte { panic("internal TX does not have etxData") } -func (tx *InternalTx) etxAccessList() AccessList { panic("internal TX does not have etxAccessList") } +func (tx *InternalTx) txType() byte { return InternalTxType } +func (tx *InternalTx) chainID() *big.Int { return tx.ChainID } +func (tx *InternalTx) protected() bool { return true } +func (tx *InternalTx) accessList() AccessList { return tx.AccessList } +func (tx *InternalTx) data() []byte { return tx.Data } +func (tx *InternalTx) gas() uint64 { return tx.Gas } +func (tx *InternalTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *InternalTx) gasTipCap() *big.Int { return tx.GasTipCap } +func (tx *InternalTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *InternalTx) value() *big.Int { return tx.Value } +func (tx *InternalTx) nonce() uint64 { return tx.Nonce } +func (tx *InternalTx) to() *common.Address { return tx.To } +func (tx *InternalTx) fromPubKey() sr25519.PublicKey { return tx.FromPubKey } +func (tx *InternalTx) etxGasLimit() uint64 { panic("internal TX does not have etxGasLimit") } +func (tx *InternalTx) etxGasPrice() *big.Int { panic("internal TX does not have etxGasPrice") } +func (tx *InternalTx) etxGasTip() *big.Int { panic("internal TX does not have etxGasTip") } +func (tx *InternalTx) etxData() []byte { panic("internal TX does not have etxData") } +func (tx *InternalTx) etxAccessList() AccessList { panic("internal TX does not have etxAccessList") } -func (tx *InternalTx) rawSignatureValues() (v, r, s *big.Int) { - return tx.V, tx.R, tx.S +func (tx *InternalTx) rawSignatureValues() []byte { + return tx.Signature } -func (tx *InternalTx) setSignatureValues(chainID, v, r, s *big.Int) { - tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +func (tx *InternalTx) setSignatureValues(chainID *big.Int, sig []byte) { + tx.ChainID, tx.Signature = chainID, sig } diff --git a/core/types/transaction.go b/core/types/transaction.go index 7dc8005426..8bfd2b7749 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -27,7 +27,7 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/common/math" - "github.com/dominant-strategies/go-quai/crypto" + "github.com/dominant-strategies/go-quai/crypto/sr25519" "github.com/dominant-strategies/go-quai/rlp" ) @@ -84,14 +84,15 @@ type TxData interface { value() *big.Int nonce() uint64 to() *common.Address + fromPubKey() sr25519.PublicKey etxGasLimit() uint64 etxGasPrice() *big.Int etxGasTip() *big.Int etxData() []byte etxAccessList() AccessList - rawSignatureValues() (v, r, s *big.Int) - setSignatureValues(chainID, v, r, s *big.Int) + rawSignatureValues() []byte + setSignatureValues(chainID *big.Int, sig []byte) } // EncodeRLP implements rlp.Encoder @@ -181,10 +182,10 @@ func (tx *Transaction) setDecoded(inner TxData, size int) { } } -func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int) error { - if !crypto.ValidateSignatureValues(byte(v.Uint64()), r, s) { - return ErrInvalidSig - } +func sanityCheckSignature(sig []byte) error { + // if !crypto.ValidateSignatureValues(byte(v.Uint64()), r, s) { + // return ErrInvalidSig + // } return nil } @@ -276,6 +277,12 @@ func (tx *Transaction) To() *common.Address { return &cpy } +// To returns the recipient address of the transaction. +// For contract-creation transactions, To returns nil. +func (tx *Transaction) FromPubKey() sr25519.PublicKey { + return tx.inner.fromPubKey() +} + // Cost returns gas * gasPrice + value. func (tx *Transaction) Cost() *big.Int { total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) @@ -285,7 +292,7 @@ func (tx *Transaction) Cost() *big.Int { // RawSignatureValues returns the V, R, S signature values of the transaction. // The return values should not be modified by the caller. -func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { +func (tx *Transaction) RawSignatureValues() []byte { return tx.inner.rawSignatureValues() } @@ -409,12 +416,12 @@ func (tx *Transaction) Size() common.StorageSize { // WithSignature returns a new transaction with the given signature. // This signature needs to be in the [R || S || V] format where V is 0 or 1. func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) { - r, s, v, err := signer.SignatureValues(tx, sig) - if err != nil { - return nil, err - } + // r, s, v, err := signer.SignatureValues(tx, sig) + // if err != nil { + // return nil, err + // } cpy := tx.inner.copy() - cpy.setSignatureValues(signer.ChainID(), v, r, s) + cpy.setSignatureValues(signer.ChainID(), sig) return &Transaction{inner: cpy, time: tx.time}, nil } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index e7b26298f1..52f7e48b0f 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -41,10 +41,8 @@ type txJSON struct { AccessList *AccessList `json:"accessList"` // Optional fields only present for internal transactions - ChainID *hexutil.Big `json:"chainId,omitempty"` - V *hexutil.Big `json:"v,omitempty"` - R *hexutil.Big `json:"r,omitempty"` - S *hexutil.Big `json:"s,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + Signature *hexutil.Bytes `json:"sig,omitempty"` // Optional fields only present for external transactions Sender *common.Address `json:"sender,omitempty"` @@ -78,9 +76,7 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.Value = (*hexutil.Big)(tx.Value) enc.Data = (*hexutil.Bytes)(&tx.Data) enc.To = t.To() - enc.V = (*hexutil.Big)(tx.V) - enc.R = (*hexutil.Big)(tx.R) - enc.S = (*hexutil.Big)(tx.S) + enc.Signature = (*hexutil.Bytes)(&tx.Signature) case *ExternalTx: enc.ChainID = (*hexutil.Big)(tx.ChainID) enc.AccessList = &tx.AccessList @@ -102,9 +98,7 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.Value = (*hexutil.Big)(tx.Value) enc.Data = (*hexutil.Bytes)(&tx.Data) enc.To = t.To() - enc.V = (*hexutil.Big)(tx.V) - enc.R = (*hexutil.Big)(tx.R) - enc.S = (*hexutil.Big)(tx.S) + enc.Signature = (*hexutil.Bytes)(&tx.Signature) enc.ETXGasLimit = (*hexutil.Uint64)(&tx.ETXGasLimit) enc.ETXGasPrice = (*hexutil.Big)(tx.ETXGasPrice) enc.ETXGasTip = (*hexutil.Big)(tx.ETXGasTip) @@ -162,21 +156,10 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'input' in internal transaction") } itx.Data = *dec.Data - if dec.V == nil { - return errors.New("missing required field 'v' in internal transaction") - } - itx.V = (*big.Int)(dec.V) - if dec.R == nil { - return errors.New("missing required field 'r' in internal transaction") - } - itx.R = (*big.Int)(dec.R) - if dec.S == nil { - return errors.New("missing required field 's' in internal transaction") - } - itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature && itx.txType() != ExternalTxType { - if err := sanityCheckSignature(itx.V, itx.R, itx.S); err != nil { + itx.Signature = *dec.Signature + // withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + if itx.txType() != ExternalTxType { + if err := sanityCheckSignature(itx.Signature); err != nil { return err } } @@ -262,24 +245,14 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'input' in internalToExternal transaction") } itx.Data = *dec.Data - if dec.V == nil { - return errors.New("missing required field 'v' in internalToExternal transaction") - } - itx.V = (*big.Int)(dec.V) - if dec.R == nil { - return errors.New("missing required field 'r' in internalToExternal transaction") - } - itx.R = (*big.Int)(dec.R) - if dec.S == nil { - return errors.New("missing required field 's' in internalToExternal transaction") - } - itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature && itx.txType() != ExternalTxType { - if err := sanityCheckSignature(itx.V, itx.R, itx.S); err != nil { - return err - } - } + itx.Signature = *dec.Signature + // itx.S = (*big.Int)(dec.S) + // withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + // if withSignature && itx.txType() != ExternalTxType { + // if err := sanityCheckSignature(itx.V, itx.R, itx.S); err != nil { + // return err + // } + // } if dec.ETXGasLimit == nil { return errors.New("missing required field 'etxGasLimit' in internalToExternal transaction") } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 60260849a9..c64c9eeca1 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -24,6 +24,7 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/crypto" + "github.com/dominant-strategies/go-quai/crypto/sr25519" "github.com/dominant-strategies/go-quai/params" ) @@ -165,14 +166,36 @@ func (s SignerV1) Sender(tx *Transaction) (common.Address, error) { if tx.Type() == ExternalTxType { // External TX does not have a signature return tx.inner.(*ExternalTx).Sender, nil } - V, R, S := tx.RawSignatureValues() - // DynamicFee txs are defined to use 0 and 1 as their recovery - // id, add 27 to become equivalent to unprotected signatures. - V = new(big.Int).Add(V, big.NewInt(27)) - if tx.ChainId().Cmp(s.chainId) != 0 { - return common.ZeroAddr, ErrInvalidChainId + + // switch sigType := tx.SigType(); sigType { + switch (tx.FromPubKey() == sr25519.PublicKey{}) { + case true: + R, S, V, err := s.SignatureValues(tx, tx.RawSignatureValues()) + if err != nil { + return common.ZeroAddr, err + } + + // DynamicFee txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.ZeroAddr, ErrInvalidChainId + } + return recoverPlain(s.Hash(tx), R, S, V) + case false: + from := tx.FromPubKey() + fmt.Println(&from) + decodedPub := from.Encode() + hex := from.Hex() + fmt.Println(hex) + + err := sr25519.VerifySignature(decodedPub, tx.RawSignatureValues(), s.Hash(tx).Bytes()) + if err != nil { + return common.ZeroAddr, err + } + return from.Address(), err } - return recoverPlain(s.Hash(tx), R, S, V) + return common.ZeroAddr, fmt.Errorf("unknown signature type") } func (s SignerV1) Equal(s2 Signer) bool { diff --git a/core/types/transaction_signing_test.go b/core/types/transaction_signing_test.go new file mode 100644 index 0000000000..2a554b615c --- /dev/null +++ b/core/types/transaction_signing_test.go @@ -0,0 +1,108 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "fmt" + "math/big" + "testing" + + "github.com/dominant-strategies/go-quai/crypto" + "github.com/dominant-strategies/go-quai/crypto/sr25519" + "github.com/stretchr/testify/require" +) + +func TestInternalSigningSecp256k(t *testing.T) { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + fmt.Println(&addr) + signer := NewSigner(big.NewInt(18)) + txData := &InternalTx{ + Nonce: 0, + To: &addr, + Gas: 21000, + Value: big.NewInt(1), + ChainID: big.NewInt(1), + GasTipCap: big.NewInt(1), + GasFeeCap: big.NewInt(1), + } + tx, err := SignTx(NewTx(txData), signer, key) + if err != nil { + t.Fatal(err) + } + + from, err := Sender(signer, tx) + if err != nil { + t.Fatal(err) + } + if from.String() != addr.String() { + t.Errorf("exected from and address to be equal. Got %x want %x", from, addr) + } +} + +func TestInternalSigningRistretto(t *testing.T) { + keypair, err := sr25519.GenerateKeypair() + require.NoError(t, err) + + addr := keypair.Public().Address() + fmt.Println(keypair.Public().Hex()) + fmt.Println(&addr) + + // encoded := keypair.Public().Encode() + // addr := common.BytesToAddress(encoded) + + pub := keypair.Public().(*sr25519.PublicKey) + + signer := NewSigner(big.NewInt(1)) + txData := &InternalTx{ + Nonce: 0, + FromPubKey: *pub, + To: &addr, + Gas: 21000, + Value: big.NewInt(1), + ChainID: big.NewInt(1), + GasTipCap: big.NewInt(1), + GasFeeCap: big.NewInt(1), + } + + tx := NewTx(txData) + msg := signer.Hash(tx) + + sig, err := keypair.Sign(msg.Bytes()) + if err != nil { + t.Fatal(err) + } + + fmt.Println(sig) + + tx, err = tx.WithSignature(signer, sig) + if err != nil { + t.Fatal(err) + } + + from, err := Sender(signer, tx) + if err != nil { + t.Fatal(err) + } + + // Stupid check here because we are just loading the from address in Sender from the InternalTx. + // Need to improve to actually check the sig. + if from.String() != addr.String() { + t.Errorf("exected from and address to be equal. Got %x want %x", from, addr) + } +} diff --git a/crypto/keypair.go b/crypto/keypair.go new file mode 100644 index 0000000000..0b1d3f6a6f --- /dev/null +++ b/crypto/keypair.go @@ -0,0 +1,101 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package crypto + +import ( + bip39 "github.com/cosmos/go-bip39" + "github.com/dominant-strategies/go-quai/common" + "golang.org/x/crypto/blake2b" +) + +// KeyType str +type KeyType = string + +// Ed25519Type ed25519 +const Ed25519Type KeyType = "ed25519" + +// Sr25519Type sr25519 +const Sr25519Type KeyType = "sr25519" + +// Secp256k1Type secp256k1 +const Secp256k1Type KeyType = "secp256k1" + +// UnknownType is used by the GenericKeystore +const UnknownType KeyType = "unknown" + +// PublicKey interface +type PublicKey interface { + Verify(msg, sig []byte) (bool, error) + Encode() []byte + Decode([]byte) error + Address() common.Address + Hex() string +} + +// PrivateKey interface +type PrivateKey interface { + Sign(msg []byte) ([]byte, error) + Public() (PublicKey, error) + Encode() []byte + Decode([]byte) error + Hex() string +} + +var ss58Prefix = []byte("SS58PRE") + +const ( + // PublicKeyLength is the expected public key length for sr25519. + PublicKeyLength = 32 + // SeedLength is the expected seed length for sr25519. + SeedLength = 32 + // PrivateKeyLength is the expected private key length for sr25519. + PrivateKeyLength = 32 + // SignatureLength is the expected signature length for sr25519. + SignatureLengthEd25519 = 64 + // VRFOutputLength is the expected VFR output length for sr25519. + VRFOutputLength = 32 + // VRFProofLength is the expected VFR proof length for sr25519. + VRFProofLength = 64 +) + +// PublicKeyToAddress returns an ss58 address given a PublicKey +// see: https://github.com/paritytech/substrate/wiki/External-Address-Format-(SS58) +// also see: https://github.com/paritytech/substrate/blob/master/primitives/core/src/crypto.rs#L275 +func PublicKeyToAddress(pub PublicKey) common.Address { + enc := append([]byte{42}, pub.Encode()...) + return publicKeyBytesToAddress(enc) +} + +func publicKeyBytesToAddress(b []byte) common.Address { + hasher, err := blake2b.New(64, nil) + if err != nil { + return common.Address{} + } + _, err = hasher.Write(append(ss58Prefix, b...)) + if err != nil { + return common.Address{} + } + checksum := hasher.Sum(nil) + return common.BytesToAddress(append(b, checksum[:2]...)) +} + +// PublicAddressToByteArray returns []byte address for given PublicKey Address +func PublicAddressToByteArray(add common.Address) []byte { + if (add == common.Address{}) { + return nil + } + // k := base58.Decode(add.String()) + // return k[1:33] + return add.Bytes() +} + +// NewBIP39Mnemonic returns a new BIP39-compatible mnemonic +func NewBIP39Mnemonic() (string, error) { + entropy, err := bip39.NewEntropy(128) + if err != nil { + return "", err + } + + return bip39.NewMnemonic(entropy) +} diff --git a/crypto/sr25519/sr25519_signing.go b/crypto/sr25519/sr25519_signing.go new file mode 100644 index 0000000000..96faf40bdb --- /dev/null +++ b/crypto/sr25519/sr25519_signing.go @@ -0,0 +1,434 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package sr25519 + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" + + sr25519 "github.com/ChainSafe/go-schnorrkel" + "github.com/dominant-strategies/go-quai/common" + "github.com/dominant-strategies/go-quai/crypto" + "github.com/gtank/merlin" +) + +// SigningContext is the context for signatures used or created with substrate +var SigningContext = []byte("substrate") + +// Keypair is a sr25519 public-private keypair +type Keypair struct { + public *PublicKey + private *PrivateKey +} + +// PublicKey holds reference to a sr25519.PublicKey +type PublicKey struct { + key *sr25519.PublicKey +} + +// PrivateKey holds reference to a sr25519.SecretKey +type PrivateKey struct { + key *sr25519.SecretKey +} + +var ErrSignatureVerificationFailed = errors.New("failed to verify signature") + +// VerifySignature verifies a signature given a public key and a message +func VerifySignature(publicKey, signature, message []byte) error { + pubKey, err := NewPublicKey(publicKey) + if err != nil { + return fmt.Errorf("sr25519: %w", err) + } + + ok, err := pubKey.Verify(message, signature) + if err != nil { + return fmt.Errorf("sr25519: %w", err) + } else if !ok { + return fmt.Errorf("sr25519: %w: for message 0x%x, signature 0x%x and public key 0x%x", + ErrSignatureVerificationFailed, message, signature, publicKey) + } + + return nil +} + +// NewKeypair returns a sr25519 Keypair given a schnorrkel secret key +func NewKeypair(priv *sr25519.SecretKey) (*Keypair, error) { + pub, err := priv.Public() + if err != nil { + return nil, err + } + + return &Keypair{ + public: &PublicKey{key: pub}, + private: &PrivateKey{key: priv}, + }, nil +} + +// NewKeypairFromPrivate returns a sr25519 Keypair given a *sr25519.PrivateKey +func NewKeypairFromPrivate(priv *PrivateKey) (*Keypair, error) { + pub, err := priv.Public() + if err != nil { + return nil, err + } + + return &Keypair{ + public: pub.(*PublicKey), + private: priv, + }, nil +} + +// NewKeypairFromSeed returns a new sr25519 Keypair given a seed +func NewKeypairFromSeed(keystr []byte) (*Keypair, error) { + if len(keystr) != crypto.SeedLength { + return nil, errors.New("cannot generate key from seed: seed is not 32 bytes long") + } + + buf := [crypto.SeedLength]byte{} + copy(buf[:], keystr) + msc, err := sr25519.NewMiniSecretKeyFromRaw(buf) + if err != nil { + return nil, err + } + + priv := msc.ExpandEd25519() + pub := msc.Public() + + return &Keypair{ + public: &PublicKey{key: pub}, + private: &PrivateKey{key: priv}, + }, nil +} + +// HexToBytes turns a 0x prefixed hex string into a byte slice +func HexToBytes(in string) (b []byte, err error) { + if !strings.HasPrefix(in, "0x") { + return nil, fmt.Errorf("%w: %s", errors.New("rr no prefix"), in) + } + + b, err = hex.DecodeString(in[2:]) + if err != nil { + return nil, fmt.Errorf("%w: %s", err, in) + } + + return b, nil +} + +// NewKeypairFromPrivateKeyString returns a Keypair given a 0x prefixed private key string +func NewKeypairFromPrivateKeyString(in string) (*Keypair, error) { + privBytes, err := HexToBytes(in) + if err != nil { + return nil, err + } + + return NewKeypairFromPrivateKeyBytes(privBytes) +} + +// NewKeypairFromPrivateKeyBytes returns a Keypair given a private key byte slice +func NewKeypairFromPrivateKeyBytes(in []byte) (*Keypair, error) { + priv, err := NewPrivateKey(in) + if err != nil { + return nil, err + } + + pub, err := priv.Public() + if err != nil { + return nil, err + } + + return &Keypair{ + private: priv, + public: pub.(*PublicKey), + }, nil +} + +// NewKeypairFromMnenomic returns a new Keypair using the given mnemonic and password. +func NewKeypairFromMnenomic(mnemonic, password string) (*Keypair, error) { + msc, err := sr25519.MiniSecretKeyFromMnemonic(mnemonic, password) + if err != nil { + return nil, err + } + + priv := msc.ExpandEd25519() + pub := msc.Public() + + return &Keypair{ + public: &PublicKey{key: pub}, + private: &PrivateKey{key: priv}, + }, nil +} + +// NewPrivateKey creates a new private key using the input bytes +func NewPrivateKey(in []byte) (*PrivateKey, error) { + if len(in) != crypto.PrivateKeyLength { + return nil, errors.New("input to create sr25519 private key is not 32 bytes") + } + priv := new(PrivateKey) + err := priv.Decode(in) + return priv, err +} + +// GenerateKeypair returns a new sr25519 keypair +func GenerateKeypair() (*Keypair, error) { + priv, pub, err := sr25519.GenerateKeypair() + if err != nil { + return nil, err + } + + return &Keypair{ + public: &PublicKey{key: pub}, + private: &PrivateKey{key: priv}, + }, nil +} + +// NewPublicKey returns a sr25519 public key from 32 byte input +func NewPublicKey(in []byte) (*PublicKey, error) { + if len(in) != crypto.PublicKeyLength { + return nil, errors.New("cannot create public key: input is not 32 bytes") + } + + buf := [crypto.PublicKeyLength]byte{} + copy(buf[:], in) + + sr25519Key, err := sr25519.NewPublicKey(buf) + if err != nil { + return nil, fmt.Errorf("creating sr25519 public key: %w", err) + } + + return &PublicKey{key: sr25519Key}, nil +} + +// Type returns Sr25519Type +func (*Keypair) Type() crypto.KeyType { + return crypto.Sr25519Type +} + +// Sign uses the keypair to sign the message using the sr25519 signature algorithm +func (kp *Keypair) Sign(msg []byte) ([]byte, error) { + return kp.private.Sign(msg) +} + +// Public returns the public key corresponding to this keypair +func (kp *Keypair) Public() crypto.PublicKey { + return kp.public +} + +// Private returns the private key corresponding to this keypair +func (kp *Keypair) Private() crypto.PrivateKey { + return kp.private +} + +// VrfSign creates a VRF output and proof from a message and private key +func (kp *Keypair) VrfSign(t *merlin.Transcript) ([crypto.VRFOutputLength]byte, [crypto.VRFProofLength]byte, error) { + return kp.private.VrfSign(t) +} + +// Sign uses the private key to sign the message using the sr25519 signature algorithm +func (k *PrivateKey) Sign(msg []byte) ([]byte, error) { + if k.key == nil { + return nil, errors.New("key is nil") + } + t := sr25519.NewSigningContext(SigningContext, msg) + sig, err := k.key.Sign(t) + if err != nil { + return nil, err + } + enc := sig.Encode() + return enc[:], nil +} + +// VrfSign creates a VRF output and proof from a message and private key +func (k *PrivateKey) VrfSign(t *merlin.Transcript) ([crypto.VRFOutputLength]byte, [crypto.VRFProofLength]byte, error) { + inout, proof, err := k.key.VrfSign(t) + if err != nil { + return [32]byte{}, [64]byte{}, err + } + out := inout.Output().Encode() + proofb := proof.Encode() + return out, proofb, nil +} + +// Public returns the public key corresponding to this private key +func (k *PrivateKey) Public() (crypto.PublicKey, error) { + if k.key == nil { + return nil, errors.New("key is nil") + } + pub, err := k.key.Public() + if err != nil { + return nil, err + } + return &PublicKey{key: pub}, nil +} + +// Encode returns the 32-byte encoding of the private key +func (k *PrivateKey) Encode() []byte { + if k.key == nil { + return nil + } + enc := k.key.Encode() + return enc[:] +} + +// Decode decodes the input bytes into a private key and sets the receiver the decoded key +// Input must be 32 bytes, or else this function will error +func (k *PrivateKey) Decode(in []byte) error { + if len(in) != crypto.PrivateKeyLength { + return errors.New("input to sr25519 private key decode is not 32 bytes") + } + b := [crypto.PrivateKeyLength]byte{} + copy(b[:], in) + k.key = &sr25519.SecretKey{} + return k.key.Decode(b) +} + +// Hex returns the private key as a '0x' prefixed hex string +func (k *PrivateKey) Hex() string { + enc := k.Encode() + h := hex.EncodeToString(enc) + return "0x" + h +} + +// Verify uses the sr25519 signature algorithm to verify that the message was signed by +// this public key; it returns true if this key created the signature for the message, +// false otherwise +func (k *PublicKey) Verify(msg, sig []byte) (bool, error) { + if k.key == nil { + return false, errors.New("nil public key") + } + + if len(sig) != crypto.SignatureLengthEd25519 { + return false, errors.New("invalid signature length") + } + + b := [crypto.SignatureLengthEd25519]byte{} + copy(b[:], sig) + + s := &sr25519.Signature{} + err := s.Decode(b) + if err != nil { + return false, err + } + + t := sr25519.NewSigningContext(SigningContext, msg) + return k.key.Verify(s, t) +} + +// VerifyDeprecated verifies that the public key signed the given message. +// Deprecated: this is used by ext_crypto_sr25519_verify_version_1 only and should not be used anywhere else. +// This method does not check that the signature is in fact a schnorrkel signature, and does not +// distinguish between sr25519 and ed25519 signatures. +func (k *PublicKey) VerifyDeprecated(msg, sig []byte) (bool, error) { + if k.key == nil { + return false, errors.New("nil public key") + } + + if len(sig) != crypto.SignatureLengthEd25519 { + return false, errors.New("invalid signature length") + } + + b := [crypto.SignatureLengthEd25519]byte{} + copy(b[:], sig) + + s := &sr25519.Signature{} + err := s.DecodeNotDistinguishedFromEd25519(b) + if err != nil { + return false, err + } + + t := sr25519.NewSigningContext(SigningContext, msg) + ok, err := k.key.Verify(s, t) + if err != nil { + return false, fmt.Errorf("verifying signature for sr25519 signing context: %w", err) + } else if ok { + return true, nil + } + + t = merlin.NewTranscript(string(SigningContext)) + t.AppendMessage([]byte("sign-bytes"), msg) + ok, err = k.key.Verify(s, t) + if err != nil { + return false, fmt.Errorf("verifying signature for merlin transcript: %w", err) + } + + return ok, nil +} + +// VrfVerify confirms that the output and proof are valid given a message and public key +func (k *PublicKey) VrfVerify(t *merlin.Transcript, out [crypto.VRFOutputLength]byte, + proof [crypto.VRFProofLength]byte) (bool, error) { + o := new(sr25519.VrfOutput) + err := o.Decode(out) + if err != nil { + return false, err + } + + p := new(sr25519.VrfProof) + err = p.Decode(proof) + if err != nil { + return false, err + } + + sr25519Key, err := sr25519.NewOutput(out) + if err != nil { + return false, fmt.Errorf("creating sr25519 key: %w", err) + } + + return k.key.VrfVerify(t, sr25519Key, p) +} + +// Encode returns the 32-byte encoding of the public key +func (k *PublicKey) Encode() []byte { + if k.key == nil { + return nil + } + + enc := k.key.Encode() + return enc[:] +} + +// Decode decodes the input bytes into a public key and sets the receiver the decoded key +// Input must be 32 bytes, or else this function will error +func (k *PublicKey) Decode(in []byte) error { + if len(in) != crypto.PublicKeyLength { + return errors.New("input to sr25519 public key decode is not 32 bytes") + } + b := [crypto.PublicKeyLength]byte{} + copy(b[:], in) + k.key = &sr25519.PublicKey{} + return k.key.Decode(b) +} + +// Address returns the ss58 address for this public key +func (k *PublicKey) Address() common.Address { + return crypto.PublicKeyToAddress(k) +} + +// Hex returns the public key as a '0x' prefixed hex string +func (k *PublicKey) Hex() string { + enc := k.Encode() + h := hex.EncodeToString(enc) + return "0x" + h +} + +// AsBytes returns the key as a [crypto.PublicKeyLength]byte +func (k *PublicKey) AsBytes() [crypto.PublicKeyLength]byte { + return k.key.Encode() +} + +// AttachInput wraps schnorrkel *VrfOutput.AttachInput +func AttachInput(output [crypto.VRFOutputLength]byte, pub *PublicKey, t *merlin.Transcript) ( + vrfInOut *sr25519.VrfInOut, err error) { + out, err := sr25519.NewOutput(output) + if err != nil { + return nil, fmt.Errorf("creating sr25519 output: %w", err) + } + + vrfInOut, err = out.AttachInput(pub.key, t) + if err != nil { + return nil, fmt.Errorf("attaching input: %w", err) + } + + return vrfInOut, nil +} diff --git a/crypto/sr25519/sr25519_signing_test.go b/crypto/sr25519/sr25519_signing_test.go new file mode 100644 index 0000000000..81891bdc56 --- /dev/null +++ b/crypto/sr25519/sr25519_signing_test.go @@ -0,0 +1,182 @@ +package sr25519 + +import ( + "crypto/rand" + "errors" + "fmt" + "testing" + + bip39 "github.com/cosmos/go-bip39" + "github.com/gtank/merlin" + "github.com/stretchr/testify/require" +) + +func TestNewKeypairFromSeed(t *testing.T) { + seed := make([]byte, 32) + _, err := rand.Read(seed) + require.NoError(t, err) + + kp, err := NewKeypairFromSeed(seed) + require.NoError(t, err) + require.NotNil(t, kp.public) + require.NotNil(t, kp.private) + + seed = make([]byte, 20) + _, err = rand.Read(seed) + require.NoError(t, err) + kp, err = NewKeypairFromSeed(seed) + require.Nil(t, kp) + require.Error(t, err, "cannot generate key from seed: seed is not 32 bytes long") +} + +func TestSignAndVerify(t *testing.T) { + kp, err := GenerateKeypair() + require.NoError(t, err) + + msg := []byte("helloworld") + sig, err := kp.Sign(msg) + require.NoError(t, err) + + pub := kp.Public().(*PublicKey) + ok, err := pub.Verify(msg, sig) + require.NoError(t, err) + require.True(t, ok) +} + +func TestPublicKeys(t *testing.T) { + kp, err := GenerateKeypair() + require.NoError(t, err) + + priv := kp.Private().(*PrivateKey) + kp2, err := NewKeypair(priv.key) + require.NoError(t, err) + require.Equal(t, kp.Public(), kp2.Public()) +} + +func TestEncodeAndDecodePrivateKey(t *testing.T) { + kp, err := GenerateKeypair() + require.NoError(t, err) + + enc := kp.Private().Encode() + res := new(PrivateKey) + err = res.Decode(enc) + require.NoError(t, err) + + exp := kp.Private().(*PrivateKey).key.Encode() + require.Equal(t, exp, res.key.Encode()) +} + +func TestEncodeAndDecodePublicKey(t *testing.T) { + kp, err := GenerateKeypair() + require.NoError(t, err) + + enc := kp.Public().Encode() + res := new(PublicKey) + err = res.Decode(enc) + require.NoError(t, err) + + exp := kp.Public().(*PublicKey).key.Encode() + require.Equal(t, exp, res.key.Encode()) +} + +func TestVrfSignAndVerify(t *testing.T) { + kp, err := GenerateKeypair() + require.NoError(t, err) + + transcript := merlin.NewTranscript("helloworld") + out, proof, err := kp.VrfSign(transcript) + require.NoError(t, err) + + pub := kp.Public().(*PublicKey) + transcript2 := merlin.NewTranscript("helloworld") + ok, err := pub.VrfVerify(transcript2, out, proof) + require.NoError(t, err) + require.True(t, ok) +} + +func TestSignAndVerify_Deprecated(t *testing.T) { + kp, err := GenerateKeypair() + require.NoError(t, err) + + msg := []byte("helloworld") + sig, err := kp.Sign(msg) + require.NoError(t, err) + + pub := kp.Public().(*PublicKey) + address := pub.Address() + fmt.Println("address", address) + ok, err := pub.VerifyDeprecated(msg, sig) + require.NoError(t, err) + require.True(t, ok) +} + +func TestNewKeypairFromMnenomic(t *testing.T) { + entropy, err := bip39.NewEntropy(128) + require.NoError(t, err) + + mnemonic, err := bip39.NewMnemonic(entropy) + require.NoError(t, err) + + _, err = NewKeypairFromMnenomic(mnemonic, "") + require.NoError(t, err) +} + +func TestVerifySignature(t *testing.T) { + t.Parallel() + + keypair, err := GenerateKeypair() + require.NoError(t, err) + + publicKey := keypair.public.Encode() + + message := []byte("Hello world!") + + signature, err := keypair.Sign(message) + require.NoError(t, err) + + testCase := map[string]struct { + publicKey, signature, message []byte + err error + }{ + "success": { + publicKey: publicKey, + signature: signature, + message: message, + }, + "bad_public_key_input": { + publicKey: []byte{}, + signature: signature, + message: message, + err: errors.New("sr25519: cannot create public key: input is not 32 bytes"), + }, + "invalid_signature_length": { + publicKey: publicKey, + signature: []byte{}, + message: message, + err: fmt.Errorf("sr25519: invalid signature length"), + }, + "verification_failed": { + publicKey: publicKey, + signature: signature, + message: []byte("a225e8c75da7da319af6335e7642d473"), + err: fmt.Errorf("sr25519: %w: for message 0x%x, signature 0x%x and public key 0x%x", + errors.New("failed to verify signature"), []byte("a225e8c75da7da319af6335e7642d473"), signature, publicKey), + }, + } + + for name, value := range testCase { + testCase := value + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := VerifySignature(testCase.publicKey, testCase.signature, testCase.message) + + if testCase.err != nil { + require.EqualError(t, err, testCase.err.Error()) + return + } + require.NoError(t, err) + }) + } + +} diff --git a/go.mod b/go.mod index 05bc86e8fd..6997dbe78b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/Azure/azure-storage-blob-go v0.7.0 github.com/VictoriaMetrics/fastcache v1.6.0 - github.com/btcsuite/btcd v0.20.1-beta + github.com/btcsuite/btcd v0.23.0 github.com/cespare/cp v0.1.0 github.com/cockroachdb/pebble v0.0.0-20230701135918-609ae80aea41 github.com/davecgh/go-spew v1.1.1 @@ -58,18 +58,24 @@ require ( ) require ( + github.com/ChainSafe/go-schnorrkel v1.1.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/btcsuite/btcutil v1.0.2 github.com/buger/jsonparser v1.1.1 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/redact v1.1.3 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230613231145-182959a1fad6 // indirect + github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f // indirect + github.com/gtank/ristretto255 v0.1.2 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -78,6 +84,7 @@ require ( github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 6e571ee9a8..eabe293c8c 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ChainSafe/go-schnorrkel v1.1.0 h1:rZ6EU+CZFCjB4sHUE1jIu8VDoB/wRKZxoe1tkcO71Wk= +github.com/ChainSafe/go-schnorrkel v1.1.0/go.mod h1:ABkENxiP+cvjFiByMIZ9LYbRoNNLeBLiakC1XeTFxfE= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -75,11 +77,26 @@ github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+Wji github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -114,6 +131,8 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcju github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= +github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -123,6 +142,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -242,6 +264,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -281,6 +307,7 @@ github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -357,6 +384,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -387,6 +416,7 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= @@ -524,6 +554,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -562,6 +593,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=