-
Notifications
You must be signed in to change notification settings - Fork 145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Revert ECDSA hash/packing logic to previous iteration #1669
Changes from 4 commits
e099ac4
9fa198e
c1b6c37
21f6ebd
4a659c8
cdea8bc
14c54ef
39df06e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,19 +99,61 @@ class EcdsaSignature { | |
* let isValid = sig.verify(msg, pk); | ||
* isValid.assertTrue('signature verifies'); | ||
* ``` | ||
* @deprecated There is a security vulnerability in this method. Use {@link verifyV2} instead. | ||
*/ | ||
verify(message: Bytes, publicKey: FlexiblePoint) { | ||
let msgHashBytes = Keccak.ethereum(message); | ||
let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve); | ||
return this.verifySignedHash(msgHash, publicKey); | ||
} | ||
|
||
/** | ||
* Verify the ECDSA signature given the message (an array of bytes) and public key (a {@link Curve} point). | ||
* | ||
* **Important:** This method returns a {@link Bool} which indicates whether the signature is valid. | ||
* So, to actually prove validity of a signature, you need to assert that the result is true. | ||
* | ||
* @throws if one of the signature scalars is zero or if the public key is not on the curve. | ||
* | ||
* @example | ||
* ```ts | ||
* // create classes for your curve | ||
* class Secp256k1 extends createForeignCurve(Crypto.CurveParams.Secp256k1) {} | ||
* class Scalar extends Secp256k1.Scalar {} | ||
* class Ecdsa extends createEcdsa(Secp256k1) {} | ||
* | ||
* let message = 'my message'; | ||
* let messageBytes = new TextEncoder().encode(message); | ||
* | ||
* // outside provable code: create inputs | ||
* let privateKey = Scalar.random(); | ||
* let publicKey = Secp256k1.generator.scale(privateKey); | ||
* let signature = Ecdsa.sign(messageBytes, privateKey.toBigInt()); | ||
* | ||
* // ... | ||
* // in provable code: create input witnesses (or use method inputs, or constants) | ||
* let pk = Provable.witness(Secp256k1.provable, () => publicKey); | ||
* let msg = Provable.witness(Provable.Array(Field, 9), () => messageBytes.map(Field)); | ||
* let sig = Provable.witness(Ecdsa.provable, () => signature); | ||
* | ||
* // verify signature | ||
* let isValid = sig.verify(msg, pk); | ||
* isValid.assertTrue('signature verifies'); | ||
* ``` | ||
*/ | ||
verifyV2(message: Bytes, publicKey: FlexiblePoint) { | ||
let msgHashBytes = Keccak.ethereum(message); | ||
let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve); | ||
return this.verifySignedHashV2(msgHash, publicKey); | ||
} | ||
|
||
/** | ||
* Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). | ||
* | ||
* This is a building block of {@link EcdsaSignature.verify}, where the input message is also hashed. | ||
* In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in | ||
* choosing the hashing algorithm. | ||
* @deprecated There is a security vulnerability in this method. Use {@link verifySignedHashV2} instead. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you completely remove any other doccomments here except the deprecation notice? we don't want to encourage people to use it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
*/ | ||
verifySignedHash( | ||
msgHash: AlmostForeignField | bigint, | ||
|
@@ -127,6 +169,27 @@ class EcdsaSignature { | |
); | ||
} | ||
|
||
/** | ||
* Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point). | ||
* | ||
* This is a building block of {@link EcdsaSignature.verify}, where the input message is also hashed. | ||
* In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in | ||
* choosing the hashing algorithm. | ||
*/ | ||
verifySignedHashV2( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was it necessary to add a new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah thanks for adding this as well! |
||
msgHash: AlmostForeignField | bigint, | ||
publicKey: FlexiblePoint | ||
) { | ||
let msgHash_ = this.Constructor.Curve.Scalar.from(msgHash); | ||
let publicKey_ = this.Constructor.Curve.from(publicKey); | ||
return Ecdsa.verifyV2( | ||
this.Constructor.Curve.Bigint, | ||
toObject(this), | ||
msgHash_.value, | ||
toPoint(publicKey_) | ||
); | ||
} | ||
|
||
/** | ||
* Create an {@link EcdsaSignature} by signing a message with a private key. | ||
* | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -212,33 +212,32 @@ function equals(p1: Point, p2: point, Curve: { modulus: bigint }) { | |||||
return xEquals.and(yEquals); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Verify an ECDSA signature. | ||||||
* | ||||||
* Details about the `config` parameter: | ||||||
* - For both the generator point `G` and public key `P`, `config` allows you to specify: | ||||||
* - the `windowSize` which is used in scalar multiplication for this point. | ||||||
* this flexibility is good because the optimal window size is different for constant and non-constant points. | ||||||
* empirically, `windowSize=4` for constants and 3 for variables leads to the fewest constraints. | ||||||
* our defaults reflect that the generator is always constant and the public key is variable in typical applications. | ||||||
* - a table of multiples of those points, of length `2^windowSize`, which is used in the scalar multiplication gadget to speed up the computation. | ||||||
* if these are not provided, they are computed on the fly. | ||||||
* for the constant G, computing multiples costs no constraints, so passing them in makes no real difference. | ||||||
* for variable public key, there is a possible use case: if the public key is a public input, then its multiples could also be. | ||||||
* in that case, passing them in would avoid computing them in-circuit and save a few constraints. | ||||||
* - The initial aggregator `ia`, see {@link initialAggregator}. By default, `ia` is computed deterministically on the fly. | ||||||
*/ | ||||||
function verifyEcdsa( | ||||||
function verifyEcdsaGeneric( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactored some of the duplicated logic between both verify functions and specified behaviour with a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice |
||||||
Curve: CurveAffine, | ||||||
signature: Ecdsa.Signature, | ||||||
msgHash: Field3, | ||||||
publicKey: Point, | ||||||
multiScalarMul: ( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adds a parameter to specify the version of |
||||||
scalars: Field3[], | ||||||
points: Point[], | ||||||
Curve: CurveAffine, | ||||||
tableConfigs?: ( | ||||||
| { | ||||||
windowSize?: number; | ||||||
multiples?: Point[]; | ||||||
} | ||||||
| undefined | ||||||
)[], | ||||||
mode?: 'assert-nonzero' | 'assert-zero', | ||||||
ia?: point, | ||||||
hashed?: boolean | ||||||
) => Point, | ||||||
config: { | ||||||
G?: { windowSize: number; multiples?: Point[] }; | ||||||
P?: { windowSize: number; multiples?: Point[] }; | ||||||
ia?: point; | ||||||
} = { G: { windowSize: 4 }, P: { windowSize: 4 } } | ||||||
) { | ||||||
): Bool { | ||||||
// constant case | ||||||
if ( | ||||||
EcdsaSignature.isConstant(signature) && | ||||||
|
@@ -285,6 +284,83 @@ function verifyEcdsa( | |||||
return Provable.equal(Field3.provable, Rx, r); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Verify an ECDSA signature. | ||||||
* | ||||||
* Details about the `config` parameter: | ||||||
* - For both the generator point `G` and public key `P`, `config` allows you to specify: | ||||||
* - the `windowSize` which is used in scalar multiplication for this point. | ||||||
* this flexibility is good because the optimal window size is different for constant and non-constant points. | ||||||
* empirically, `windowSize=4` for constants and 3 for variables leads to the fewest constraints. | ||||||
* our defaults reflect that the generator is always constant and the public key is variable in typical applications. | ||||||
* - a table of multiples of those points, of length `2^windowSize`, which is used in the scalar multiplication gadget to speed up the computation. | ||||||
* if these are not provided, they are computed on the fly. | ||||||
* for the constant G, computing multiples costs no constraints, so passing them in makes no real difference. | ||||||
* for variable public key, there is a possible use case: if the public key is a public input, then its multiples could also be. | ||||||
* in that case, passing them in would avoid computing them in-circuit and save a few constraints. | ||||||
* - The initial aggregator `ia`, see {@link initialAggregator}. By default, `ia` is computed deterministically on the fly. | ||||||
* @deprecated There is a security vulnerability with this function, allowing a prover to modify witness values than what is expected. Use {@link verifyEcdsaV2} instead. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you completely remove any other doccomments here except the deprecation notice? we don't want to encourage people to use it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
*/ | ||||||
function verifyEcdsa( | ||||||
Curve: CurveAffine, | ||||||
signature: Ecdsa.Signature, | ||||||
msgHash: Field3, | ||||||
publicKey: Point, | ||||||
config: { | ||||||
G?: { windowSize: number; multiples?: Point[] }; | ||||||
P?: { windowSize: number; multiples?: Point[] }; | ||||||
ia?: point; | ||||||
} = { G: { windowSize: 4 }, P: { windowSize: 4 } } | ||||||
) { | ||||||
return verifyEcdsaGeneric( | ||||||
Curve, | ||||||
signature, | ||||||
msgHash, | ||||||
publicKey, | ||||||
(scalars, points, Curve, configs, mode, ia) => | ||||||
multiScalarMul(scalars, points, Curve, configs, mode, ia, true), | ||||||
config | ||||||
); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Verify an ECDSA signature. | ||||||
* | ||||||
* Details about the `config` parameter: | ||||||
* - For both the generator point `G` and public key `P`, `config` allows you to specify: | ||||||
* - the `windowSize` which is used in scalar multiplication for this point. | ||||||
* this flexibility is good because the optimal window size is different for constant and non-constant points. | ||||||
* empirically, `windowSize=4` for constants and 3 for variables leads to the fewest constraints. | ||||||
* our defaults reflect that the generator is always constant and the public key is variable in typical applications. | ||||||
* - a table of multiples of those points, of length `2^windowSize`, which is used in the scalar multiplication gadget to speed up the computation. | ||||||
* if these are not provided, they are computed on the fly. | ||||||
* for the constant G, computing multiples costs no constraints, so passing them in makes no real difference. | ||||||
* for variable public key, there is a possible use case: if the public key is a public input, then its multiples could also be. | ||||||
* in that case, passing them in would avoid computing them in-circuit and save a few constraints. | ||||||
* - The initial aggregator `ia`, see {@link initialAggregator}. By default, `ia` is computed deterministically on the fly. | ||||||
*/ | ||||||
function verifyEcdsaV2( | ||||||
Curve: CurveAffine, | ||||||
signature: Ecdsa.Signature, | ||||||
msgHash: Field3, | ||||||
publicKey: Point, | ||||||
config: { | ||||||
G?: { windowSize: number; multiples?: Point[] }; | ||||||
P?: { windowSize: number; multiples?: Point[] }; | ||||||
ia?: point; | ||||||
} = { G: { windowSize: 4 }, P: { windowSize: 4 } } | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you try changing the default back to this?
Suggested change
it should use fewer constraints There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed here! Here is the lower constraints |
||||||
) { | ||||||
return verifyEcdsaGeneric( | ||||||
Curve, | ||||||
signature, | ||||||
msgHash, | ||||||
publicKey, | ||||||
(scalars, points, Curve, configs, mode, ia) => | ||||||
multiScalarMul(scalars, points, Curve, configs, mode, ia, false), | ||||||
config | ||||||
); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Bigint implementation of ECDSA verify | ||||||
*/ | ||||||
|
@@ -311,6 +387,36 @@ function verifyEcdsaConstant( | |||||
return Curve.Scalar.equal(R.x, r); | ||||||
} | ||||||
|
||||||
function multiScalarMulConstant( | ||||||
scalars: Field3[], | ||||||
points: Point[], | ||||||
Curve: CurveAffine, | ||||||
mode: 'assert-nonzero' | 'assert-zero' = 'assert-nonzero' | ||||||
): Point { | ||||||
let n = points.length; | ||||||
assert(scalars.length === n, 'Points and scalars lengths must match'); | ||||||
assertPositiveInteger(n, 'Expected at least 1 point and scalar'); | ||||||
let useGlv = Curve.hasEndomorphism; | ||||||
|
||||||
// TODO dedicated MSM | ||||||
let s = scalars.map(Field3.toBigint); | ||||||
let P = points.map(Point.toBigint); | ||||||
let sum = Curve.zero; | ||||||
for (let i = 0; i < n; i++) { | ||||||
if (useGlv) { | ||||||
sum = Curve.add(sum, Curve.Endo.scale(P[i], s[i])); | ||||||
} else { | ||||||
sum = Curve.add(sum, Curve.scale(P[i], s[i])); | ||||||
} | ||||||
} | ||||||
if (mode === 'assert-zero') { | ||||||
assert(sum.infinity, 'scalar multiplication: expected zero result'); | ||||||
return Point.from(Curve.zero); | ||||||
} | ||||||
assert(!sum.infinity, 'scalar multiplication: expected non-zero result'); | ||||||
return Point.from(sum); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Multi-scalar multiplication: | ||||||
* | ||||||
|
@@ -339,7 +445,8 @@ function multiScalarMul( | |||||
| undefined | ||||||
)[] = [], | ||||||
mode: 'assert-nonzero' | 'assert-zero' = 'assert-nonzero', | ||||||
ia?: point | ||||||
ia?: point, | ||||||
hashed: boolean = true | ||||||
): Point { | ||||||
let n = points.length; | ||||||
assert(scalars.length === n, 'Points and scalars lengths must match'); | ||||||
|
@@ -348,23 +455,7 @@ function multiScalarMul( | |||||
|
||||||
// constant case | ||||||
if (scalars.every(Field3.isConstant) && points.every(Point.isConstant)) { | ||||||
// TODO dedicated MSM | ||||||
let s = scalars.map(Field3.toBigint); | ||||||
let P = points.map(Point.toBigint); | ||||||
let sum = Curve.zero; | ||||||
for (let i = 0; i < n; i++) { | ||||||
if (useGlv) { | ||||||
sum = Curve.add(sum, Curve.Endo.scale(P[i], s[i])); | ||||||
} else { | ||||||
sum = Curve.add(sum, Curve.scale(P[i], s[i])); | ||||||
} | ||||||
} | ||||||
if (mode === 'assert-zero') { | ||||||
assert(sum.infinity, 'scalar multiplication: expected zero result'); | ||||||
return Point.from(Curve.zero); | ||||||
} | ||||||
assert(!sum.infinity, 'scalar multiplication: expected non-zero result'); | ||||||
return Point.from(sum); | ||||||
return multiScalarMulConstant(scalars, points, Curve, mode); | ||||||
} | ||||||
|
||||||
// parse or build point tables | ||||||
|
@@ -444,14 +535,22 @@ function multiScalarMul( | |||||
if (i % windowSize === 0) { | ||||||
// pick point to add based on the scalar chunk | ||||||
let sj = scalarChunks[j][i / windowSize]; | ||||||
let sjP = | ||||||
windowSize === 1 | ||||||
? points[j] | ||||||
: arrayGetGeneric( | ||||||
HashedPoint.provable, | ||||||
hashedTables[j], | ||||||
sj | ||||||
).unhash(); | ||||||
let sjP; | ||||||
if (hashed) { | ||||||
sjP = | ||||||
windowSize === 1 | ||||||
? points[j] | ||||||
: arrayGetGeneric( | ||||||
HashedPoint.provable, | ||||||
hashedTables[j], | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it costs constraints to create the could you also add a check there and make it an empty array when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
sj | ||||||
).unhash(); | ||||||
} else { | ||||||
sjP = | ||||||
windowSize === 1 | ||||||
? points[j] | ||||||
: arrayGetGeneric(Point.provable, tables[j], sj); | ||||||
} | ||||||
|
||||||
// ec addition | ||||||
let added = add(sum, sjP, Curve); | ||||||
|
@@ -725,6 +824,7 @@ const EcdsaSignature = { | |||||
const Ecdsa = { | ||||||
sign: signEcdsa, | ||||||
verify: verifyEcdsa, | ||||||
verifyV2: verifyEcdsaV2, | ||||||
Signature: EcdsaSignature, | ||||||
}; | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you completely remove any other doccomments here except the deprecation notice? we don't want to encourage people to use it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed