@@ -58,22 +58,22 @@ where
5858 presignature : Uint8Array ,
5959 signature_shares : Vec < Uint8Array > ,
6060 ) -> JsResult < EcdsaSignature > {
61- let ( big_r, s) = Self :: combine_inner ( presignature, signature_shares) ?;
62- Self :: signature_into_js ( big_r. to_affine ( ) , s)
61+ let ( big_r, s, was_flipped ) = Self :: combine_inner ( presignature, signature_shares) ?;
62+ Self :: signature_into_js ( big_r. to_affine ( ) , s, was_flipped )
6363 }
6464
6565 pub ( crate ) fn combine_inner (
6666 presignature : Uint8Array ,
6767 signature_shares : Vec < Uint8Array > ,
68- ) -> JsResult < ( C :: ProjectivePoint , C :: Scalar ) > {
68+ ) -> JsResult < ( C :: ProjectivePoint , C :: Scalar , bool ) > {
6969 let signature_shares = signature_shares
7070 . into_iter ( )
7171 . map ( Self :: scalar_from_js)
7272 . collect :: < JsResult < Vec < _ > > > ( ) ?;
7373
7474 let big_r: C :: AffinePoint = Self :: point_from_js ( presignature) ?;
75- let s = Self :: sum_scalars ( signature_shares) ?;
76- Ok ( ( C :: ProjectivePoint :: from ( big_r) , s) )
75+ let ( s , was_flipped ) = Self :: sum_scalars ( signature_shares) ?;
76+ Ok ( ( C :: ProjectivePoint :: from ( big_r) , s, was_flipped ) )
7777 }
7878
7979 pub fn verify (
@@ -108,13 +108,14 @@ where
108108 Ok ( ( ) )
109109 }
110110
111- fn sum_scalars ( values : Vec < C :: Scalar > ) -> JsResult < C :: Scalar > {
111+ fn sum_scalars ( values : Vec < C :: Scalar > ) -> JsResult < ( C :: Scalar , bool ) > {
112112 if values. is_empty ( ) {
113113 return Err ( JsError :: new ( "no shares provided" ) ) ;
114114 }
115115 let mut acc: C :: Scalar = values. into_iter ( ) . sum ( ) ;
116+ let acc_flipped = acc. is_high ( ) . into ( ) ;
116117 acc. conditional_assign ( & ( -acc) , acc. is_high ( ) ) ;
117- Ok ( acc)
118+ Ok ( ( acc, acc_flipped ) )
118119 }
119120
120121 pub fn derive_key ( id : Uint8Array , public_keys : Vec < Uint8Array > ) -> JsResult < Uint8Array > {
@@ -168,10 +169,15 @@ where
168169 Ok ( ( r, s, v) )
169170 }
170171
171- fn signature_into_js ( big_r : C :: AffinePoint , s : C :: Scalar ) -> JsResult < EcdsaSignature > {
172+ fn signature_into_js ( big_r : C :: AffinePoint , s : C :: Scalar , was_flipped : bool ) -> JsResult < EcdsaSignature > {
172173 let r = Self :: x_coordinate ( & big_r) . to_repr ( ) ;
173174 let s = s. to_repr ( ) ;
174- let v = u8:: conditional_select ( & 0 , & 1 , big_r. y_is_odd ( ) ) ;
175+ let mut v = u8:: conditional_select ( & 0 , & 1 , big_r. y_is_odd ( ) ) ;
176+
177+ // Flip v if s was normalized (flipped, low-s rule)
178+ if was_flipped {
179+ v = 1 - v;
180+ }
175181
176182 Ok ( EcdsaSignature {
177183 obj : into_js ( & ( Bytes :: new ( & r) , Bytes :: new ( & s) , v) ) ?,
@@ -222,7 +228,7 @@ where
222228 public_key : C :: ProjectivePoint ,
223229 ) -> JsResult < EcdsaSignature > {
224230 let z = Self :: scalar_from_hash ( message_hash) ?;
225- let ( big_r, s) = Self :: combine_inner ( pre_signature, signature_shares) ?;
231+ let ( big_r, s, was_flipped ) = Self :: combine_inner ( pre_signature, signature_shares) ?;
226232 let r = Self :: x_coordinate ( & big_r. to_affine ( ) ) ;
227233
228234 if z. is_zero ( ) . into ( ) {
@@ -241,7 +247,7 @@ where
241247 . is_identity ( )
242248 . into ( )
243249 {
244- Self :: signature_into_js ( big_r. to_affine ( ) , s)
250+ Self :: signature_into_js ( big_r. to_affine ( ) , s, was_flipped )
245251 } else {
246252 Err ( JsError :: new ( "invalid signature" ) )
247253 }
0 commit comments