diff --git a/common.go b/common.go index 9a66f8e..723f634 100644 --- a/common.go +++ b/common.go @@ -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...) } diff --git a/common_test.go b/common_test.go index 1ff6f08..48be985 100644 --- a/common_test.go +++ b/common_test.go @@ -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}, }, } { diff --git a/constant.go b/constant.go index 1cb8935..6e57953 100644 --- a/constant.go +++ b/constant.go @@ -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 diff --git a/ecdsa.go b/ecdsa.go index da2d89a..4ae4118 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -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 diff --git a/eddsa.go b/eddsa.go index 330fcf9..71797b8 100644 --- a/eddsa.go +++ b/eddsa.go @@ -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 diff --git a/rsa.go b/rsa.go index e2eba6d..41b080e 100644 --- a/rsa.go +++ b/rsa.go @@ -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