Skip to content

Commit 1dc90e4

Browse files
authored
Merge pull request #2005 from o1-labs/feature/reusable-ecdsa
Make ECDSA easier to use with alternative hash functions
2 parents e1d70be + 7cbac24 commit 1dc90e4

File tree

3 files changed

+41
-13
lines changed

3 files changed

+41
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1818
## [Unreleased](https://github.com/o1-labs/o1js/compare/b857516...HEAD)
1919

2020
### Added
21+
2122
- `setFee` and `setFeePerSnarkCost` for `Transaction` and `PendingTransaction` https://github.com/o1-labs/o1js/pull/1968
2223
- Doc comments for various ZkProgram methods https://github.com/o1-labs/o1js/pull/1974
2324
- `MerkleList.popOption()` for popping the last element and also learning if there was one https://github.com/o1-labs/o1js/pull/1997
2425

2526
### Changed
27+
2628
- Sort order for actions now includes the transaction sequence number and the exact account id sequence https://github.com/o1-labs/o1js/pull/1917
2729
- Updated typedoc version for generating docs https://github.com/o1-labs/o1js/pull/1973
30+
- ECDSA `verifySignedHash()` accepts hash `Bytes` directly for easy use with alternative hash functions https://github.com/o1-labs/o1js/pull/2005
2831

2932
### Fixed
3033

@@ -381,7 +384,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
381384
- `Reducer.reduce()` requires the maximum number of actions per method as an explicit (optional) argument https://github.com/o1-labs/o1js/pull/1450
382385
- The default value is 1 and should work for most existing contracts
383386
- `new UInt64()` and `UInt64.from()` no longer unsafely accept a field element as input. https://github.com/o1-labs/o1js/pull/1438 [@julio4](https://github.com/julio4)
384-
As a replacement, `UInt64.Unsafe.fromField()` was introduced
387+
As a replacement, `UInt64.Unsafe.fromField()` was introduced
385388
- This prevents you from accidentally creating a `UInt64` without proving that it fits in 64 bits
386389
- Equivalent changes were made to `UInt32`
387390
- Fixed vulnerability in `Field.to/fromBits()` outlined in [#1023](https://github.com/o1-labs/o1js/issues/1023) by imposing a limit of 254 bits https://github.com/o1-labs/o1js/pull/1461

src/examples/crypto/ecdsa/ecdsa.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
createForeignCurve,
66
Bool,
77
Bytes,
8+
Hash,
89
} from 'o1js';
910

1011
export { keccakAndEcdsa, ecdsa, Secp256k1, Ecdsa, Bytes32, ecdsaEthers };
@@ -62,3 +63,24 @@ const ecdsaEthers = ZkProgram({
6263
},
6364
},
6465
});
66+
67+
/**
68+
* We can also use a different hash function with ECDSA, like SHA-256.
69+
*/
70+
const sha256AndEcdsa = ZkProgram({
71+
name: 'ecdsa-sha256',
72+
publicInput: Bytes32,
73+
publicOutput: Bool,
74+
75+
methods: {
76+
verifyEcdsa: {
77+
privateInputs: [Ecdsa, Secp256k1],
78+
async method(message: Bytes32, signature: Ecdsa, publicKey: Secp256k1) {
79+
let messageHash = Hash.SHA2_256.hash(message);
80+
return {
81+
publicOutput: signature.verifySignedHash(messageHash, publicKey),
82+
};
83+
},
84+
},
85+
},
86+
});

src/lib/provable/crypto/foreign-ecdsa.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,7 @@ class EcdsaSignature {
104104
*/
105105
verify(message: Bytes, publicKey: FlexiblePoint): Bool {
106106
let msgHashBytes = Keccak.ethereum(message);
107-
let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve);
108-
return this.verifySignedHash(msgHash, publicKey);
107+
return this.verifySignedHash(msgHashBytes, publicKey);
109108
}
110109

111110
/**
@@ -155,22 +154,23 @@ class EcdsaSignature {
155154
...Bytes.fromString(String(message.length)).bytes, // message length as string
156155
...message.bytes, // actual message bytes
157156
]);
158-
159-
let msgHash = keccakOutputToScalar(msgHashBytes, this.Constructor.Curve);
160-
return this.verifySignedHash(msgHash, publicKey);
157+
return this.verifySignedHash(msgHashBytes, publicKey);
161158
}
162159

163160
/**
164161
* Verify the ECDSA signature given the message hash (a {@link Scalar}) and public key (a {@link Curve} point).
165162
*
166163
* This is a building block of {@link EcdsaSignature.verify}, where the input message is also hashed.
167-
* In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in
168-
* choosing the hashing algorithm.
164+
* In contrast, this method just takes the message hash (a curve scalar, or the output bytes of a hash function)
165+
* as input, giving you flexibility in choosing the hashing algorithm.
169166
*/
170167
verifySignedHash(
171-
msgHash: AlmostForeignField | bigint,
168+
msgHash: AlmostForeignField | bigint | Bytes,
172169
publicKey: FlexiblePoint
173170
): Bool {
171+
if (msgHash instanceof Bytes.Base)
172+
msgHash = keccakOutputToScalar(msgHash, this.Constructor.Curve);
173+
174174
let msgHash_ = this.Constructor.Curve.Scalar.from(msgHash);
175175
let publicKey_ = this.Constructor.Curve.from(publicKey);
176176
return Ecdsa.verify(
@@ -196,12 +196,15 @@ class EcdsaSignature {
196196
* Create an {@link EcdsaSignature} by signing a message hash with a private key.
197197
*
198198
* This is a building block of {@link EcdsaSignature.sign}, where the input message is also hashed.
199-
* In contrast, this method just takes the message hash (a curve scalar) as input, giving you flexibility in
200-
* choosing the hashing algorithm.
199+
* In contrast, this method just takes the message hash (a curve scalar, or the output bytes of a hash function)
200+
* as input, giving you flexibility in choosing the hashing algorithm.
201201
*
202-
* Note: This method is not provable, and only takes JS bigints as input.
202+
* Note: This method is not provable, and only takes JS bigints or constant Bytes as input.
203203
*/
204-
static signHash(msgHash: bigint, privateKey: bigint) {
204+
static signHash(msgHash: bigint | Bytes, privateKey: bigint) {
205+
if (msgHash instanceof Bytes.Base)
206+
msgHash = keccakOutputToScalar(msgHash, this.Curve).toBigInt();
207+
205208
let { r, s } = Ecdsa.sign(this.Curve.Bigint, msgHash, privateKey);
206209
return new this({ r, s });
207210
}

0 commit comments

Comments
 (0)