// Copyright (c) 2018-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package txscript import ( "errors" "testing" ) // TestCheckSignatureEncoding ensures that checking strict signature encoding // works as expected. func TestCheckSignatureEncoding(t *testing.T) { t.Parallel() tests := []struct { name string sig []byte err error }{{ // signature from Decred blockchain tx // 76634e947f49dfc6228c3e8a09cd3e9e15893439fc06df7df0fc6f08d659856c:0 name: "valid signature 1", sig: hexToBytes("3045022100cd496f2ab4fe124f977ffe3caa09f7576d8a34156" + "b4e55d326b4dffc0399a094022013500a0510b5094bff220c74656879b8ca03" + "69d3da78004004c970790862fc03"), err: nil, }, { // signature from Decred blockchain tx // 76634e947f49dfc6228c3e8a09cd3e9e15893439fc06df7df0fc6f08d659856c:1 name: "valid signature 2", sig: hexToBytes("3044022036334e598e51879d10bf9ce3171666bc2d1bbba6164" + "cf46dd1d882896ba35d5d022056c39af9ea265c1b6d7eab5bc977f06f81e35c" + "dcac16f3ec0fd218e30f2bad2a"), err: nil, }, { name: "empty", sig: nil, err: ErrSigTooShort, }, { name: "too short", sig: hexToBytes("30050201000200"), err: ErrSigTooShort, }, { name: "too long", sig: hexToBytes("3045022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef074022030e09575e7a1541aa018876a4003cefe1b061a" + "90556b5140c63e0ef8481352480101"), err: ErrSigTooLong, }, { name: "bad ASN.1 sequence id", sig: hexToBytes("3145022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef074022030e09575e7a1541aa018876a4003cefe1b061a" + "90556b5140c63e0ef848135248"), err: ErrSigInvalidSeqID, }, { name: "mismatched data length (short one byte)", sig: hexToBytes("3044022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef074022030e09575e7a1541aa018876a4003cefe1b061a" + "90556b5140c63e0ef848135248"), err: ErrSigInvalidDataLen, }, { name: "mismatched data length (long one byte)", sig: hexToBytes("3046022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef074022030e09575e7a1541aa018876a4003cefe1b061a" + "90556b5140c63e0ef848135248"), err: ErrSigInvalidDataLen, }, { name: "bad R ASN.1 int marker", sig: hexToBytes("304403204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d6" + "24c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56c" + "bbac4622082221a8768d1d09"), err: ErrSigInvalidRIntID, }, { name: "zero R length", sig: hexToBytes("30240200022030e09575e7a1541aa018876a4003cefe1b061a90" + "556b5140c63e0ef848135248"), err: ErrSigZeroRLen, }, { name: "negative R (too little padding)", sig: hexToBytes("30440220b2ec8d34d473c3aa2ab5eb7cc4a0783977e5db8c8daf" + "777e0b6d7bfa6b6623f302207df6f09af2c40460da2c2c5778f636d3b2e27e20" + "d10d90f5a5afb45231454700"), err: ErrSigNegativeR, }, { name: "too much R padding", sig: hexToBytes("304402200077f6e93de5ed43cf1dfddaa79fca4b766e1a8fc879" + "b0333d377f62538d7eb5022054fed940d227ed06d6ef08f320976503848ed1f5" + "2d0dd6d17f80c9c160b01d86"), err: ErrSigTooMuchRPadding, }, { name: "bad S ASN.1 int marker", sig: hexToBytes("3045022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef074032030e09575e7a1541aa018876a4003cefe1b061a" + "90556b5140c63e0ef848135248"), err: ErrSigInvalidSIntID, }, { name: "missing S ASN.1 int marker", sig: hexToBytes("3023022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef074"), err: ErrSigMissingSTypeID, }, { name: "S length missing", sig: hexToBytes("3024022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef07402"), err: ErrSigMissingSLen, }, { name: "invalid S length (short one byte)", sig: hexToBytes("3045022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef074021f30e09575e7a1541aa018876a4003cefe1b061a" + "90556b5140c63e0ef848135248"), err: ErrSigInvalidSLen, }, { name: "invalid S length (long one byte)", sig: hexToBytes("3045022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef074022130e09575e7a1541aa018876a4003cefe1b061a" + "90556b5140c63e0ef848135248"), err: ErrSigInvalidSLen, }, { name: "zero S length", sig: hexToBytes("3025022100f5353150d31a63f4a0d06d1f5a01ac65f7267a719e" + "49f2a1ac584fd546bef0740200"), err: ErrSigZeroSLen, }, { name: "negative S (too little padding)", sig: hexToBytes("304402204fc10344934662ca0a93a84d14d650d8a21cf2ab91f6" + "08e8783d2999c955443202208441aacd6b17038ff3f6700b042934f9a6fea0ce" + "c2051b51dc709e52a5bb7d61"), err: ErrSigNegativeS, }, { name: "too much S padding", sig: hexToBytes("304402206ad2fdaf8caba0f2cb2484e61b81ced77474b4c2aa06" + "9c852df1351b3314fe20022000695ad175b09a4a41cd9433f6b2e8e83253d6a7" + "402096ba313a7be1f086dde5"), err: ErrSigTooMuchSPadding, }, { // Although signatures with R == 0 will ultimately be invalid, it is // considered a valid encoding from the standpoint of preventing // malleability. name: "R == 0", sig: hexToBytes("30250201000220181522ec8eca07de4860a4acdd12909d831cc5" + "6cbbac4622082221a8768d1d09"), err: nil, }, { // Although signatures with R == N will ultimately be invalid, it is // considered a valid encoding from the standpoint of preventing // malleability. name: "R == N", sig: hexToBytes("3045022100fffffffffffffffffffffffffffffffebaaedce6af" + "48a03bbfd25e8cd03641410220181522ec8eca07de4860a4acdd12909d831cc5" + "6cbbac4622082221a8768d1d09"), err: nil, }, { // Although signatures with R > N will ultimately be invalid, it is // considered a valid encoding from the standpoint of preventing // malleability. name: "R > N (>32 bytes)", sig: hexToBytes("3045022101cd496f2ab4fe124f977ffe3caa09f756283910fc1a" + "96f60ee6873e88d3cfe1d50220181522ec8eca07de4860a4acdd12909d831cc5" + "6cbbac4622082221a8768d1d09"), err: nil, }, { // Although signatures with R > N will ultimately be invalid, it is // considered a valid encoding from the standpoint of preventing // malleability. name: "R > N", sig: hexToBytes("3045022100fffffffffffffffffffffffffffffffebaaedce6af" + "48a03bbfd25e8cd03641420220181522ec8eca07de4860a4acdd12909d831cc5" + "6cbbac4622082221a8768d1d09"), err: nil, }, { // Although signatures with S == 0 will ultimately be invalid, it is // considered a valid encoding from the standpoint of preventing // malleability. name: "S == 0", sig: hexToBytes("302502204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d6" + "24c6c61548ab5fb8cd41020100"), err: nil, }, { name: "S > N/2 (half order)", sig: hexToBytes("304602210080e256f8a9df823ff0322c5515fc4d4538d65a3785" + "fb6dd1b448af216864318d022100cfbf242e941d77555bd79fadd3d23b49d3ca" + "929459fa114247e55ff8b4fcf832"), err: ErrSigHighS, }, { name: "S == N", sig: hexToBytes("304502204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d6" + "24c6c61548ab5fb8cd41022100fffffffffffffffffffffffffffffffebaaedc" + "e6af48a03bbfd25e8cd0364141"), err: ErrSigHighS, }, { name: "S > N (>32 bytes)", sig: hexToBytes("304502204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d6" + "24c6c61548ab5fb8cd4102210113500a0510b5094bff220c74656879b784b246" + "ba89c0a07bc49bcf05d8993d44"), err: ErrSigHighS, }, { name: "S > N", sig: hexToBytes("304502204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d6" + "24c6c61548ab5fb8cd41022100fffffffffffffffffffffffffffffffebaaedc" + "e6af48a03bbfd25e8cd0364142"), err: ErrSigHighS, }} for _, test := range tests { err := CheckSignatureEncoding(test.sig) if !errors.Is(err, test.err) { t.Errorf("%s mismatched err -- got %v, want %v", test.name, err, test.err) } } } // TestCheckPubKeyEncoding ensures that checking strict public key encoding // works as expected. func TestCheckPubKeyEncoding(t *testing.T) { t.Parallel() tests := []struct { name string key []byte err error }{{ name: "uncompressed ok", key: hexToBytes("04" + "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" + "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3"), err: nil, }, { name: "compressed ok (ybit = 0)", key: hexToBytes("02" + "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d"), err: nil, }, { name: "compressed ok (ybit = 1)", key: hexToBytes("03" + "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e"), err: nil, }, { // Although public keys not on the curve will ultimately be invalid, it // is considered a valid encoding from the standpoint of preventing // malleability. name: "uncompressed x changed (not on curve)", key: hexToBytes("04" + "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" + "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3"), err: nil, }, { // Although public keys not on the curve will ultimately be invalid, it // is considered a valid encoding from the standpoint of preventing // malleability. name: "uncompressed y changed (not on curve)", key: hexToBytes("04" + "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" + "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4"), err: nil, }, { name: "empty rejected", key: nil, err: ErrPubKeyType, }, { name: "hybrid rejected (ybit = 0)", key: hexToBytes("06" + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"), err: ErrPubKeyType, }, { name: "hybrid rejected (ybit = 1)", key: hexToBytes("07" + "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" + "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3"), err: ErrPubKeyType, }, { name: "uncompressed claims compressed rejected", key: hexToBytes("03" + "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" + "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3"), err: ErrPubKeyType, }, { name: "compressed claims uncompressed rejected (ybit = 0)", key: hexToBytes("04" + "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d"), err: ErrPubKeyType, }, { name: "compressed claims uncompressed rejected (ybit = 1)", key: hexToBytes("04" + "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e"), err: ErrPubKeyType, }, { name: "compressed claims hybrid rejected (ybit = 0)", key: hexToBytes("06" + "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d"), err: ErrPubKeyType, }, { name: "compressed claims hybrid rejected (ybit = 1)", key: hexToBytes("07" + "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e"), err: ErrPubKeyType, }} for _, test := range tests { err := CheckPubKeyEncoding(test.key) if !errors.Is(err, test.err) { t.Errorf("%s mismatched err -- got %v, want %v", test.name, err, test.err) } } } // TestIsStrictNullData ensures the function that deals with strict null data // requirements works as expected. func TestIsStrictNullData(t *testing.T) { tests := []struct { name string scriptVer uint16 script []byte requiredLen uint32 want bool }{{ name: "empty (bare OP_RETURN), req len 0", scriptVer: 0, script: mustParseShortFormV0("RETURN"), requiredLen: 0, want: true, }, { name: "empty (bare OP_RETURN), req len 0, unsupported script ver", scriptVer: 65535, script: mustParseShortFormV0("RETURN"), requiredLen: 0, want: false, }, { name: "small int push 0, req len 1", scriptVer: 0, script: mustParseShortFormV0("RETURN 0"), requiredLen: 1, want: true, }, { name: "small int push 0, req len 1, unsupported script ver", scriptVer: 65535, script: mustParseShortFormV0("RETURN 0"), requiredLen: 1, want: false, }, { name: "non-canonical small int push 0, req len 1", scriptVer: 0, script: mustParseShortFormV0("RETURN DATA_1 0x00"), requiredLen: 1, want: false, }, { name: "small int push 1, req len 1", scriptVer: 0, script: mustParseShortFormV0("RETURN 1"), requiredLen: 1, want: true, }, { name: "small int push 1, req len 1, unsupported script ver", scriptVer: 65535, script: mustParseShortFormV0("RETURN 1"), requiredLen: 1, want: false, }, { name: "small int push 16, req len 1", scriptVer: 0, script: mustParseShortFormV0("RETURN 16"), requiredLen: 1, want: true, }, { name: "small int push 16, req len 1, unsupported script ver", scriptVer: 65535, script: mustParseShortFormV0("RETURN 16"), requiredLen: 1, want: false, }, { name: "small int push 0, req len 2", scriptVer: 0, script: mustParseShortFormV0("RETURN 0"), requiredLen: 2, want: false, }, { name: "small int push 1, req len 2", scriptVer: 0, script: mustParseShortFormV0("RETURN 1"), requiredLen: 2, want: false, }, { name: "small int push 16, req len 2", scriptVer: 0, script: mustParseShortFormV0("RETURN 16"), requiredLen: 2, want: false, }, { name: "32-byte push, req len 32", scriptVer: 0, script: mustParseShortFormV0("RETURN DATA_32 0x0102030405060708090a0b0c" + "0d0e0f101112131415161718191a1b1c1d1e1f20"), requiredLen: 32, want: true, }, { name: "32-byte push, req len 32, unsupported script ver", scriptVer: 65535, script: mustParseShortFormV0("RETURN DATA_32 0x0102030405060708090a0b0c" + "0d0e0f101112131415161718191a1b1c1d1e1f20"), requiredLen: 32, want: false, }, { name: "32-byte push, req len 31", scriptVer: 0, script: mustParseShortFormV0("RETURN DATA_32 0x0102030405060708090a0b0c" + "0d0e0f101112131415161718191a1b1c1d1e1f20"), requiredLen: 31, want: false, }, { name: "32-byte push, req len 33", scriptVer: 0, script: mustParseShortFormV0("RETURN DATA_32 0x0102030405060708090a0b0c" + "0d0e0f101112131415161718191a1b1c1d1e1f20"), requiredLen: 33, want: false, }, { name: "32-byte push, req len 32, no leading OP_RETURN", scriptVer: 0, script: mustParseShortFormV0("DATA_32 0x0102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f20"), requiredLen: 32, want: false, }, { name: "non-canonical 32-byte push via PUSHDATA1, req len 32", scriptVer: 0, script: mustParseShortFormV0("RETURN PUSHDATA1 0x20 0x0102030405060708" + "090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), requiredLen: 32, want: false, }, { name: "non-canonical 32-byte push via PUSHDATA2, req len 32", scriptVer: 0, script: mustParseShortFormV0("RETURN PUSHDATA2 0x2000 0x01020304050607" + "08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), requiredLen: 32, want: false, }, { name: "non-canonical 32-byte push via PUSHDATA4, req len 32", scriptVer: 0, script: mustParseShortFormV0("RETURN PUSHDATA4 0x20000000 0x0102030405" + "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), requiredLen: 32, want: false, }, { name: "76-byte push, req len 76", scriptVer: 0, script: mustParseShortFormV0("RETURN PUSHDATA1 0x4c 0x0102030405060708" + "090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728" + "292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748" + "494a4b4c"), requiredLen: 76, want: false, }} for _, test := range tests { // Ensure the test data scripts are well formed. if err := checkScriptParses(0, test.script); err != nil { t.Errorf("%s: unexpected script parse failure: %v", test.name, err) continue } // Ensure the result is as expected. got := IsStrictNullData(test.scriptVer, test.script, test.requiredLen) if got != test.want { t.Errorf("%s: mismatched result -- got %v, want %v", test.name, got, test.want) } } }