feat: turns out, PayloadEncoding can be multiple values for EIP191

This commit is contained in:
Michael Muré
2025-07-24 13:53:03 +02:00
parent 2f22cb9b15
commit 35ef54f79f
6 changed files with 111 additions and 48 deletions

View File

@@ -1,5 +1,7 @@
package varsig
import "fmt"
// [IANA JOSE specification]: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms
// Ed25519 produces a varsig for EdDSA using Ed25519 curve.
@@ -58,7 +60,14 @@ func ES512(payloadEncoding PayloadEncoding, opts ...Option) (ECDSAVarsig, error)
// EIP191 produces a varsig for ECDSA using the Secp256k1 curve, Keccak256 and encoded
// with the "personal_sign" format defined by [EIP191].
// payloadEncoding must be either PayloadEncodingEIP191Raw or PayloadEncodingEIP191Cbor.
// [EIP191]: https://eips.ethereum.org/EIPS/eip-191
func EIP191(opts ...Option) (ECDSAVarsig, error) {
return NewECDSAVarsig(CurveSecp256k1, HashKeccak256, PayloadEncodingEIP191, opts...)
func EIP191(payloadEncoding PayloadEncoding, opts ...Option) (ECDSAVarsig, error) {
switch payloadEncoding {
case PayloadEncodingEIP191Raw, PayloadEncodingEIP191Cbor:
default:
return ECDSAVarsig{}, fmt.Errorf("%w for EIP191: %v", ErrUnsupportedPayloadEncoding, payloadEncoding)
}
return NewECDSAVarsig(CurveSecp256k1, HashKeccak256, payloadEncoding, opts...)
}

View File

@@ -64,8 +64,8 @@ func TestRoundTrip(t *testing.T) {
},
{
name: "EIP191",
varsig: must(varsig.EIP191()),
dataHex: "3401ec01e7011b91a303",
varsig: must(varsig.EIP191(varsig.PayloadEncodingEIP191Raw)),
dataHex: "3401ec01e7011b91c3035f",
},
// from https://github.com/hugomrdias/iso-repo/blob/main/packages/iso-ucan/test/varsig.test.js
@@ -89,16 +89,14 @@ func TestRoundTrip(t *testing.T) {
varsig: must(varsig.ES256K(varsig.PayloadEncodingVerbatim)),
dataBytes: []byte{52, 1, 236, 1, 231, 1, 18, 95},
},
// the two cases below in iso-ucan are actually EIP191 preset where the encoding is overridden
// therefore, we build them manually.
{
name: "EIP191+RAW",
varsig: must(varsig.NewECDSAVarsig(varsig.CurveSecp256k1, varsig.HashKeccak256, varsig.PayloadEncodingVerbatim)),
varsig: must(varsig.EIP191(varsig.PayloadEncodingEIP191Raw)),
dataBytes: []byte{52, 1, 236, 1, 231, 1, 27, 145, 195, 3, 95},
},
{
name: "EIP191+DAG-CBOR",
varsig: must(varsig.NewECDSAVarsig(varsig.CurveSecp256k1, varsig.HashKeccak256, varsig.PayloadEncodingDAGCBOR)),
varsig: must(varsig.EIP191(varsig.PayloadEncodingEIP191Cbor)),
dataBytes: []byte{52, 1, 236, 1, 231, 1, 27, 145, 195, 3, 113},
},
} {

View File

@@ -78,73 +78,129 @@ func DecodeHashAlgorithm(r BytesReader) (Hash, error) {
// PayloadEncoding specifies the encoding of the data being (hashed and)
// signed. A canonical representation of the data is required to produce
// consistent hashes and signatures.
type PayloadEncoding uint64
type PayloadEncoding int
// Constant values that allow Varsig implementations to specify how the
// payload content is encoded before being hashed.
// In varsig >= v1, only canonical encoding is allowed.
const (
PayloadEncodingUnspecified PayloadEncoding = 0x00
PayloadEncodingVerbatim PayloadEncoding = 0x5f
PayloadEncodingDAGPB = PayloadEncoding(0x70)
PayloadEncodingDAGCBOR = PayloadEncoding(0x71)
PayloadEncodingDAGJSON = PayloadEncoding(0x0129)
PayloadEncodingEIP191 = PayloadEncoding(0xd191)
PayloadEncodingJWT PayloadEncoding = 0x6a77
PayloadEncodingUnspecified = PayloadEncoding(iota)
PayloadEncodingVerbatim
PayloadEncodingDAGPB
PayloadEncodingDAGCBOR
PayloadEncodingDAGJSON
PayloadEncodingEIP191Raw
PayloadEncodingEIP191Cbor
PayloadEncodingJWT
)
const (
encodingSegmentVerbatim = uint64(0x5f)
encodingSegmentDAGPB = uint64(0x70)
encodingSegmentDAGCBOR = uint64(0x71)
encodingSegmentDAGJSON = uint64(0x0129)
encodingSegmentEIP191 = uint64(0xe191)
encodingSegmentJWT = uint64(0x6a77)
)
// DecodePayloadEncoding reads and validates the expected canonical payload
// encoding of the data to be signed.
func DecodePayloadEncoding(r BytesReader, vers Version) (PayloadEncoding, error) {
u, err := binary.ReadUvarint(r)
seg1, err := binary.ReadUvarint(r)
if err != nil {
return PayloadEncodingUnspecified, fmt.Errorf("%w: %w", ErrUnsupportedPayloadEncoding, err)
}
payEnc := PayloadEncoding(u)
switch vers {
case Version0:
return decodeEncodingInfoV0(payEnc)
switch seg1 {
case encodingSegmentVerbatim:
return PayloadEncodingVerbatim, nil
case encodingSegmentDAGPB:
return PayloadEncodingDAGPB, nil
case encodingSegmentDAGCBOR:
return PayloadEncodingDAGCBOR, nil
case encodingSegmentDAGJSON:
return PayloadEncodingDAGJSON, nil
case encodingSegmentEIP191:
seg2, err := binary.ReadUvarint(r)
if err != nil {
return PayloadEncodingUnspecified, fmt.Errorf("%w: incomplete EIP191 encoding: %w", ErrUnsupportedPayloadEncoding, err)
}
switch seg2 {
case encodingSegmentVerbatim:
return PayloadEncodingEIP191Raw, nil
case encodingSegmentDAGCBOR:
return PayloadEncodingEIP191Cbor, nil
default:
return PayloadEncodingUnspecified, fmt.Errorf("%w: version=%d, encoding=%x+%x", ErrUnsupportedPayloadEncoding, vers, seg1, seg2)
}
case encodingSegmentJWT:
return PayloadEncodingJWT, nil
default:
return PayloadEncodingUnspecified, fmt.Errorf("%w: version=%d, encoding=%x", ErrUnsupportedPayloadEncoding, vers, seg1)
}
case Version1:
return decodeEncodingInfoV1(payEnc)
switch seg1 {
case encodingSegmentVerbatim:
return PayloadEncodingVerbatim, nil
case encodingSegmentDAGCBOR:
return PayloadEncodingDAGCBOR, nil
case encodingSegmentDAGJSON:
return PayloadEncodingDAGJSON, nil
case encodingSegmentEIP191:
seg2, err := binary.ReadUvarint(r)
if err != nil {
return PayloadEncodingUnspecified, fmt.Errorf("%w: incomplete EIP191 encoding: %w", ErrUnsupportedPayloadEncoding, err)
}
switch seg2 {
case encodingSegmentVerbatim:
return PayloadEncodingEIP191Raw, nil
case encodingSegmentDAGCBOR:
return PayloadEncodingEIP191Cbor, nil
default:
return PayloadEncodingUnspecified, fmt.Errorf("%w: version=%d, encoding=%x+%x", ErrUnsupportedPayloadEncoding, vers, seg1, seg2)
}
default:
return PayloadEncodingUnspecified, fmt.Errorf("%w: version=%d, encoding=%x", ErrUnsupportedPayloadEncoding, vers, seg1)
}
default:
return 0, ErrUnsupportedVersion
}
}
// https://github.com/ChainAgnostic/varsig#4-payload-encoding
func decodeEncodingInfoV0(payEnc PayloadEncoding) (PayloadEncoding, error) {
switch payEnc {
case PayloadEncodingVerbatim,
PayloadEncodingDAGPB,
PayloadEncodingDAGCBOR,
PayloadEncodingDAGJSON,
PayloadEncodingJWT,
PayloadEncodingEIP191:
return payEnc, nil
// EncodePayloadEncoding returns the PayloadEncoding as serialized bytes.
// If enc is not a valid PayloadEncoding, this function will panic.
func EncodePayloadEncoding(enc PayloadEncoding) []byte {
res := make([]byte, 0, 8)
switch enc {
case PayloadEncodingVerbatim:
res = binary.AppendUvarint(res, encodingSegmentVerbatim)
case PayloadEncodingDAGPB:
res = binary.AppendUvarint(res, encodingSegmentDAGPB)
case PayloadEncodingDAGCBOR:
res = binary.AppendUvarint(res, encodingSegmentDAGCBOR)
case PayloadEncodingDAGJSON:
res = binary.AppendUvarint(res, encodingSegmentDAGJSON)
case PayloadEncodingEIP191Raw:
res = binary.AppendUvarint(res, encodingSegmentEIP191)
res = binary.AppendUvarint(res, encodingSegmentVerbatim)
case PayloadEncodingEIP191Cbor:
res = binary.AppendUvarint(res, encodingSegmentEIP191)
res = binary.AppendUvarint(res, encodingSegmentDAGCBOR)
case PayloadEncodingJWT:
res = binary.AppendUvarint(res, encodingSegmentJWT)
default:
return PayloadEncodingUnspecified, fmt.Errorf("%w: version=%d, encoding=%x", ErrUnsupportedPayloadEncoding, Version0, payEnc)
panic(fmt.Sprintf("invalid encoding: %v", enc))
}
}
// https://github.com/expede/varsig/blob/main/README.md#payload-encoding
func decodeEncodingInfoV1(payEnc PayloadEncoding) (PayloadEncoding, error) {
switch payEnc {
case PayloadEncodingVerbatim,
PayloadEncodingDAGCBOR,
PayloadEncodingDAGJSON,
PayloadEncodingEIP191:
return payEnc, nil
default:
return PayloadEncodingUnspecified, fmt.Errorf("%w: version=%d, encoding=%x", ErrUnsupportedPayloadEncoding, Version1, payEnc)
}
return res
}
// Discriminator is (usually) the value representing the public key type of
// the algorithm used to create the signature.
//
// There is not set list of constants here, nor is there a decode function
// There is no set list of constants here, nor is there a decode function
// as the author of an implementation should include the constant with the
// implementation, and the decoding is handled by the Handler, which uses
// the Discriminator to choose the correct implementation. Also note that

View File

@@ -106,7 +106,7 @@ func (v ECDSAVarsig) Encode() []byte {
}
buf = binary.AppendUvarint(buf, uint64(v.hashAlg))
buf = binary.AppendUvarint(buf, uint64(v.payEnc))
buf = append(buf, EncodePayloadEncoding(v.payEnc)...)
buf = append(buf, v.Signature()...)
return buf

View File

@@ -103,7 +103,7 @@ func (v EdDSAVarsig) Encode() []byte {
}
buf = binary.AppendUvarint(buf, uint64(v.hashAlg))
buf = binary.AppendUvarint(buf, uint64(v.payEnc))
buf = append(buf, EncodePayloadEncoding(v.payEnc)...)
buf = append(buf, v.Signature()...)
return buf

2
rsa.go
View File

@@ -51,7 +51,7 @@ func (v RSAVarsig) Encode() []byte {
buf := v.encode()
buf = binary.AppendUvarint(buf, uint64(v.hashAlg))
buf = binary.AppendUvarint(buf, v.sigLen)
buf = binary.AppendUvarint(buf, uint64(v.payEnc))
buf = append(buf, EncodePayloadEncoding(v.payEnc)...)
buf = append(buf, v.Signature()...)
return buf