Skip to content

Commit

Permalink
merge: branch '3570-add-pkcs-ecdsa-support' into 'main'
Browse files Browse the repository at this point in the history
Add general ecdsa support [#3570]

Closes #3570

See merge request accumulatenetwork/accumulate!1070
  • Loading branch information
firelizzard18 committed Jun 7, 2024
2 parents 4ce6a0b + 0cefec2 commit 5735aa4
Show file tree
Hide file tree
Showing 13 changed files with 876 additions and 70 deletions.
6 changes: 6 additions & 0 deletions internal/core/execute/v2/block/sig_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ func init() {
func(ctx *SignatureContext) bool { return ctx.GetActiveGlobals().ExecutorVersion.V2VandenbergEnabled() },
protocol.SignatureTypeRsaSha256,
)

// PKI signatures (enabled with Vandenberg)
registerConditionalExec[UserSignature](&signatureExecutors,
func(ctx *SignatureContext) bool { return ctx.GetActiveGlobals().ExecutorVersion.V2VandenbergEnabled() },
protocol.SignatureTypeEcdsaSha256,
)
}

// UserSignature processes user signatures.
Expand Down
5 changes: 5 additions & 0 deletions pkg/build/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package build

import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"fmt"
Expand Down Expand Up @@ -190,6 +191,10 @@ func (p *parser) parseKey(key any, typ protocol.SignatureType, private bool) add
return address.FromRSAPrivateKey(key)
case *rsa.PublicKey:
return address.FromRSAPublicKey(key)
case *ecdsa.PrivateKey:
return address.FromEcdsaPrivateKey(key)
case *ecdsa.PublicKey:
return address.FromEcdsaPublicKeyAsPKIX(key)
case []byte:
if typ == protocol.SignatureTypeUnknown {
if len(key) == 32 || len(key) == 64 {
Expand Down
19 changes: 19 additions & 0 deletions pkg/client/signing/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ func (s *Builder) Import(sig protocol.Signature) (*Builder, error) {
s.Timestamp = TimestampFromValue(sig.Timestamp)
s.Memo = sig.Memo
s.Data = sig.Data
case *protocol.EcdsaSha256Signature:
s.Url = sig.Signer
s.Version = sig.SignerVersion
s.Timestamp = TimestampFromValue(sig.Timestamp)
s.Memo = sig.Memo
s.Data = sig.Data
case *protocol.DelegatedSignature:
_, err := s.Import(sig.Signature)
if err != nil {
Expand Down Expand Up @@ -214,6 +220,7 @@ func (s *Builder) prepare(init bool) (protocol.KeySignature, error) {
protocol.SignatureTypeBTC,
protocol.SignatureTypeETH,
protocol.SignatureTypeRsaSha256,
protocol.SignatureTypeEcdsaSha256,
protocol.SignatureTypeBTCLegacy:

case protocol.SignatureTypeReceipt, protocol.SignatureTypePartition:
Expand Down Expand Up @@ -302,6 +309,16 @@ func (s *Builder) prepare(init bool) (protocol.KeySignature, error) {
sig.Data = s.Data
return sig, s.Signer.SetPublicKey(sig)

case protocol.SignatureTypeEcdsaSha256:
sig := new(protocol.EcdsaSha256Signature)
sig.Signer = s.Url
sig.SignerVersion = s.Version
sig.Timestamp = timestamp
sig.Vote = s.Vote
sig.Memo = s.Memo
sig.Data = s.Data
return sig, s.Signer.SetPublicKey(sig)

default:
panic("unreachable")
}
Expand All @@ -323,6 +340,8 @@ func (s *Builder) sign(sig protocol.Signature, sigMdHash, hash []byte) error {
sig.TransactionHash = *(*[32]byte)(hash)
case *protocol.RsaSha256Signature:
sig.TransactionHash = *(*[32]byte)(hash)
case *protocol.EcdsaSha256Signature:
sig.TransactionHash = *(*[32]byte)(hash)
case *protocol.DelegatedSignature:
if sigMdHash == nil {
sigMdHash = sig.Metadata().Hash()
Expand Down
13 changes: 13 additions & 0 deletions pkg/client/signing/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ func (k PrivateKey) SetPublicKey(sig protocol.Signature) error {
}
sig.PublicKey = x509.MarshalPKCS1PublicKey(&privKey.PublicKey)

case *protocol.EcdsaSha256Signature:
privKey, err := x509.ParseECPrivateKey(k)
if err != nil {
return err
}
sig.PublicKey, err = x509.MarshalPKIXPublicKey(&privKey.PublicKey)
if err != nil {
return err
}

default:
return fmt.Errorf("cannot set the public key on a %T", sig)
}
Expand Down Expand Up @@ -81,6 +91,9 @@ func (k PrivateKey) Sign(sig protocol.Signature, sigMdHash, message []byte) erro
case *protocol.RsaSha256Signature:
return protocol.SignRsaSha256(sig, k, sigMdHash, message)

case *protocol.EcdsaSha256Signature:
return protocol.SignEcdsaSha256(sig, k, sigMdHash, message)

default:
return fmt.Errorf("cannot sign %T with a key", sig)
}
Expand Down
78 changes: 72 additions & 6 deletions pkg/types/address/from.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package address

import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
Expand All @@ -29,12 +30,21 @@ func FromED25519PublicKey(key []byte) *PublicKey {
func FromED25519PrivateKey(key []byte) *PrivateKey {
switch len(key) {
default:
panic("invalid ed25519 private key")
skt, err := x509.ParsePKCS8PrivateKey(key)
if err != nil {
panic(fmt.Errorf("invalid ed25519 key length: want 32 or 64, got %d", len(key)))
}
var ok bool
key, ok = skt.(ed25519.PrivateKey)
if !ok {
panic("invalid ed25519 private key")
}
case ed25519.SeedSize:
key = ed25519.NewKeyFromSeed(key)
case ed25519.PrivateKeySize:
// Ok
}

return &PrivateKey{
PublicKey: *FromED25519PublicKey(key[32:]),
Key: key,
Expand All @@ -55,6 +65,28 @@ func FromRSAPrivateKey(key *rsa.PrivateKey) *PrivateKey {
}
}

func FromEcdsaPublicKeyAsPKIX(key *ecdsa.PublicKey) *PublicKey {
var err error
publicKey := new(PublicKey)
publicKey.Type = protocol.SignatureTypeEcdsaSha256
publicKey.Key, err = x509.MarshalPKIXPublicKey(key)
if err != nil {
panic(err)
}
return publicKey
}

func FromEcdsaPrivateKey(key *ecdsa.PrivateKey) *PrivateKey {
var err error
priv := new(PrivateKey)
priv.Type = protocol.SignatureTypeEcdsaSha256
priv.Key, err = x509.MarshalECPrivateKey(key)
if err != nil {
panic(err)
}
return priv
}

func FromPrivateKeyBytes(priv []byte, typ protocol.SignatureType) *PrivateKey {
var pub []byte
switch typ {
Expand All @@ -73,25 +105,59 @@ func FromPrivateKeyBytes(priv []byte, typ protocol.SignatureType) *PrivateKey {
case ed25519.SeedSize:
priv = ed25519.NewKeyFromSeed(priv)
default:
panic(fmt.Errorf("invalid ed25519 key length: want 32 or 64, got %d", len(priv)))
skt, err := x509.ParsePKCS8PrivateKey(priv)
if err != nil {
panic(fmt.Errorf("invalid ed25519 key length: want 32 or 64, got %d", len(priv)))
}
var ok bool
priv, ok = skt.(ed25519.PrivateKey)
if !ok {
panic(fmt.Errorf("private key is not an edcsa key"))
}
}
pub = priv[32:]

case protocol.SignatureTypeETH:
case protocol.SignatureTypeETH,
protocol.SignatureTypeBTCLegacy:
_, pk := btc.PrivKeyFromBytes(btc.S256(), priv)
pub = pk.SerializeUncompressed()

case protocol.SignatureTypeBTC,
protocol.SignatureTypeBTCLegacy:
case protocol.SignatureTypeBTC:
_, pk := btc.PrivKeyFromBytes(btc.S256(), priv)
pub = pk.SerializeCompressed()

case protocol.SignatureTypeRsaSha256:
sk, err := x509.ParsePKCS1PrivateKey(priv)
if err != nil {
panic(err)
skt, err := x509.ParsePKCS8PrivateKey(priv)
if err != nil {
panic(err)
}
var ok bool
sk, ok = skt.(*rsa.PrivateKey)
if !ok {
panic(fmt.Errorf("private key is not an rsa key"))
}
}
pub = x509.MarshalPKCS1PublicKey(&sk.PublicKey)

case protocol.SignatureTypeEcdsaSha256:
sk, err := x509.ParseECPrivateKey(priv)
if err != nil {
skt, err := x509.ParsePKCS8PrivateKey(priv)
if err != nil {
panic(err)
}
var ok bool
sk, ok = skt.(*ecdsa.PrivateKey)
if !ok {
panic(fmt.Errorf("private key is not an ecdsa key"))
}
}
pub, err = x509.MarshalPKIXPublicKey(&sk.PublicKey)
if err != nil {
panic(err)
}
}

return &PrivateKey{Key: priv, PublicKey: PublicKey{Type: typ, Key: pub}}
Expand Down
5 changes: 4 additions & 1 deletion protocol/enums.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ SignatureType:
description: is a signature produced by an authority
RsaSha256:
value: 14
description: represents an RSA signature of SHA256 hashed data
description: represents an RSA signature of SHA256 hashed data (PKCS#1 encoding)
EcdsaSha256:
value: 15
description: represents a signature of SHA256 hashed data from an ecdsa algorithm with supported standard curves from NIST, SECG, and Brainpool typically (SEC, ANS.1 enocding)

KeyPageOperationType:
Unknown:
Expand Down
11 changes: 9 additions & 2 deletions protocol/enums_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,12 @@ const SignatureTypeInternal SignatureType = 12
// SignatureTypeAuthority is a signature produced by an authority.
const SignatureTypeAuthority SignatureType = 13

// SignatureTypeRsaSha256 represents an RSA signature of SHA256 hashed data.
// SignatureTypeRsaSha256 represents an RSA signature of SHA256 hashed data (PKCS#1 encoding).
const SignatureTypeRsaSha256 SignatureType = 14

// SignatureTypeEcdsaSha256 represents a signature of SHA256 hashed data from an ecdsa algorithm with supported standard curves from NIST, SECG, and Brainpool typically (SEC, ANS.1 enocding).
const SignatureTypeEcdsaSha256 SignatureType = 15

// TransactionMaxUser is the highest number reserved for user transactions.
const TransactionMaxUser TransactionMax = 48

Expand Down Expand Up @@ -1157,7 +1160,7 @@ func (v SignatureType) GetEnumValue() uint64 { return uint64(v) }
func (v *SignatureType) SetEnumValue(id uint64) bool {
u := SignatureType(id)
switch u {
case SignatureTypeUnknown, SignatureTypeLegacyED25519, SignatureTypeED25519, SignatureTypeRCD1, SignatureTypeReceipt, SignatureTypePartition, SignatureTypeSet, SignatureTypeRemote, SignatureTypeBTC, SignatureTypeBTCLegacy, SignatureTypeETH, SignatureTypeDelegated, SignatureTypeInternal, SignatureTypeAuthority, SignatureTypeRsaSha256:
case SignatureTypeUnknown, SignatureTypeLegacyED25519, SignatureTypeED25519, SignatureTypeRCD1, SignatureTypeReceipt, SignatureTypePartition, SignatureTypeSet, SignatureTypeRemote, SignatureTypeBTC, SignatureTypeBTCLegacy, SignatureTypeETH, SignatureTypeDelegated, SignatureTypeInternal, SignatureTypeAuthority, SignatureTypeRsaSha256, SignatureTypeEcdsaSha256:
*v = u
return true
}
Expand Down Expand Up @@ -1197,6 +1200,8 @@ func (v SignatureType) String() string {
return "authority"
case SignatureTypeRsaSha256:
return "rsaSha256"
case SignatureTypeEcdsaSha256:
return "ecdsaSha256"
}
return fmt.Sprintf("SignatureType:%d", v)
}
Expand Down Expand Up @@ -1236,6 +1241,8 @@ func SignatureTypeByName(name string) (SignatureType, bool) {
return SignatureTypeAuthority, true
case "rsasha256":
return SignatureTypeRsaSha256, true
case "ecdsasha256":
return SignatureTypeEcdsaSha256, true
}
return 0, false
}
Expand Down
94 changes: 93 additions & 1 deletion protocol/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package protocol
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
Expand Down Expand Up @@ -103,7 +104,8 @@ func PublicKeyHash(key []byte, typ SignatureType) ([]byte, error) {
switch typ {
case SignatureTypeED25519,
SignatureTypeLegacyED25519,
SignatureTypeRsaSha256:
SignatureTypeRsaSha256,
SignatureTypeEcdsaSha256:
return doSha256(key), nil

case SignatureTypeRCD1:
Expand Down Expand Up @@ -1148,3 +1150,93 @@ func (e *RsaSha256Signature) Verify(sigMdHash, txnHash []byte) bool {
err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, signingHash(e, doSha256, sigMdHash, txnHash), e.Signature)
return err == nil
}

/*
* SignEcdsaSha256: Multipurpose ECDSA signature types used in PKIs
* ecdsa privateKey must be in SEC, ASN.1 DER format,
* returned signature is in the common encoding format for that particular signature type
*/
func SignEcdsaSha256(sig *EcdsaSha256Signature, privateKeyDer, sigMdHash, txnHash []byte) error {
//private key is expected to be in SEC, ASN.1 DER format
privateKey, err := x509.ParseECPrivateKey(privateKeyDer)
if err != nil {
return err
}

// Sign the signing hash and set ASN.1 DER encoded signature
sig.Signature, err = ecdsa.SignASN1(rand.Reader, privateKey, signingHash(sig, doSha256, sigMdHash, txnHash))
if err != nil {
return err
}
return nil
}

// GetSigner returns Signer.
func (s *EcdsaSha256Signature) GetSigner() *url.URL { return s.Signer }

// RoutingLocation returns Signer.
func (s *EcdsaSha256Signature) RoutingLocation() *url.URL { return s.Signer }

// GetSignerVersion returns SignerVersion.
func (s *EcdsaSha256Signature) GetSignerVersion() uint64 { return s.SignerVersion }

// GetTimestamp returns Timestamp.
func (s *EcdsaSha256Signature) GetTimestamp() uint64 { return s.Timestamp }

// GetPublicKeyHash returns the hash of PublicKey.
func (s *EcdsaSha256Signature) GetPublicKeyHash() []byte { return doSha256(s.PublicKey) }

// GetPublicKey returns PublicKey.
func (s *EcdsaSha256Signature) GetPublicKey() []byte { return s.PublicKey }

// GetSignature returns Signature.
func (s *EcdsaSha256Signature) GetSignature() []byte { return s.Signature }

// GetTransactionHash returns TransactionHash.
func (s *EcdsaSha256Signature) GetTransactionHash() [32]byte { return s.TransactionHash }

// Hash returns the hash of the signature.
func (s *EcdsaSha256Signature) Hash() []byte { return signatureHash(s) }

// Metadata returns the signature's metadata.
func (s *EcdsaSha256Signature) Metadata() Signature {
r := s.Copy() // Copy the struct
r.Signature = nil // Clear the signature
r.TransactionHash = [32]byte{} // And the transaction hash
return r
}

// Initiator returns a Hasher that calculates the Merkle hash of the signature.
func (s *EcdsaSha256Signature) Initiator() (hash.Hasher, error) {
if len(s.PublicKey) == 0 || s.Signer == nil || s.SignerVersion == 0 || s.Timestamp == 0 {
return nil, ErrCannotInitiate
}

hasher := make(hash.Hasher, 0, 4)
hasher.AddBytes(s.PublicKey)
hasher.AddUrl(s.Signer)
hasher.AddUint(s.SignerVersion)
hasher.AddUint(s.Timestamp)
return hasher, nil
}

// GetVote returns how the signer votes on a particular transaction
func (s *EcdsaSha256Signature) GetVote() VoteType {
return s.Vote
}

// Verify returns true if this signature is a valid ECDSA ANS.1 encoded signature of the
// hash. The public key is expected to be in PKCS#1 ASN.1 DER format
func (e *EcdsaSha256Signature) Verify(sigMdHash, txnHash []byte) bool {
//Convert public ANS.1 encoded key into and associated public key struct
pubKey, err := x509.ParsePKIXPublicKey(e.PublicKey)
if err != nil {
return false
}

pub, ok := pubKey.(*ecdsa.PublicKey)
if !ok {
return false
}
return ecdsa.VerifyASN1(pub, signingHash(e, doSha256, sigMdHash, txnHash), e.Signature)
}
Loading

0 comments on commit 5735aa4

Please sign in to comment.