Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14864211
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Subscribers
None
View Options
diff --git a/modules/ecash-lib-wasm/src/ecc.rs b/modules/ecash-lib-wasm/src/ecc.rs
index db661b831..3853adce9 100644
--- a/modules/ecash-lib-wasm/src/ecc.rs
+++ b/modules/ecash-lib-wasm/src/ecc.rs
@@ -1,215 +1,271 @@
// Copyright (c) 2024 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//! Module for [`Ecc`] for signing secp256k1 signatures.
use ecash_secp256k1::{
- ecdsa::{RecoverableSignature, RecoveryId},
+ ecdsa::{RecoverableSignature, RecoveryId, Signature as EcdsaSignature},
+ schnorr::Signature as SchnorrSignature,
All, Message, PublicKey, Scalar, Secp256k1, SecretKey,
};
use thiserror::Error;
use wasm_bindgen::prelude::*;
use crate::ecc::EccError::*;
/// ECC signatures with libsecp256k1.
#[derive(Debug)]
#[wasm_bindgen]
pub struct Ecc {
curve: Secp256k1<All>,
}
/// Errors indicating something went wrong with [`Ecc`].
#[derive(Error, Debug, PartialEq, Eq)]
pub enum EccError {
/// Invalid secret key size
#[error("Invalid secret key size, expected 32 bytes but got {0}")]
InvalidSeckeySize(usize),
/// Invalid secret key for signing
#[error("Secret key not valid for secp256k1")]
InvalidSeckey,
/// Invalid public key size
#[error("Invalid public key size, expected 33 bytes but got {0}")]
InvalidPubkeySize(usize),
/// Invalid pubkey key for signing
#[error("Pubkey key not valid for secp256k1")]
InvalidPubkey,
/// Invalid scalar size
#[error("Invalid scalar size, expected 32 bytes but got {0}")]
InvalidScalarSize(usize),
/// Invalid scalar in range
#[error("Scalar not valid for secp256k1")]
InvalidScalar,
/// Invalid message size
#[error("Invalid message size, expected 32 bytes but got {0}")]
InvalidMessageSize(usize),
+
+ /// Invalid DER signature format
+ #[error("Invalid DER signature format")]
+ InvalidDerSignatureFormat,
+
+ /// Invalid Schnorr signature size
+ #[error("Invalid Schnorr signature size, expected 64 bytes but got {0}")]
+ InvalidSignatureSize(usize),
+
+ /// Incorrect signature
+ #[error("Incorrect signature")]
+ IncorrectSignature,
}
impl From<EccError> for String {
fn from(ecc: EccError) -> Self {
ecc.to_string()
}
}
fn parse_secret_key(seckey: &[u8]) -> Result<SecretKey, String> {
Ok(SecretKey::from_byte_array(
&seckey
.try_into()
.map_err(|_| InvalidSeckeySize(seckey.len()))?,
)
.map_err(|_| InvalidSeckey)?)
}
fn parse_public_key(pubkey: &[u8]) -> Result<PublicKey, String> {
Ok(PublicKey::from_byte_array_compressed(
pubkey
.try_into()
.map_err(|_| InvalidPubkeySize(pubkey.len()))?,
)
.map_err(|_| InvalidPubkey)?)
}
fn parse_scalar(scalar: &[u8]) -> Result<Scalar, String> {
Ok(Scalar::from_be_bytes(
scalar
.try_into()
.map_err(|_| InvalidScalarSize(scalar.len()))?,
)
.map_err(|_| InvalidScalar)?)
}
fn parse_msg(msg: &[u8]) -> Result<Message, String> {
Ok(Message::from_digest(
msg.try_into().map_err(|_| InvalidMessageSize(msg.len()))?,
))
}
+fn parse_ecdsa_sig(sig: &[u8]) -> Result<EcdsaSignature, String> {
+ Ok(EcdsaSignature::from_der(sig).map_err(|_| InvalidDerSignatureFormat)?)
+}
+
+fn parse_schnorr_sig(sig: &[u8]) -> Result<SchnorrSignature, String> {
+ Ok(SchnorrSignature::from_slice(sig)
+ .map_err(|_| InvalidSignatureSize(sig.len()))?)
+}
+
#[wasm_bindgen]
impl Ecc {
/// Create a new Ecc instance.
#[allow(clippy::new_without_default)]
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Ecc {
curve: Secp256k1::default(),
}
}
/// Derive a public key from secret key.
#[wasm_bindgen(js_name = derivePubkey)]
pub fn derive_pubkey(&self, seckey: &[u8]) -> Result<Vec<u8>, String> {
let seckey = parse_secret_key(seckey)?;
let pubkey = PublicKey::from_secret_key(&self.curve, &seckey);
Ok(pubkey.serialize().to_vec())
}
/// Sign an ECDSA signature.
#[wasm_bindgen(js_name = ecdsaSign)]
pub fn ecdsa_sign(
&self,
seckey: &[u8],
msg: &[u8],
) -> Result<Vec<u8>, String> {
let seckey = parse_secret_key(seckey)?;
let msg = parse_msg(msg)?;
let sig = self.curve.sign_ecdsa(&msg, &seckey);
Ok(sig.serialize_der().to_vec())
}
+ /// Verify an ECDSA signature.
+ #[wasm_bindgen(js_name = ecdsaVerify)]
+ pub fn ecdsa_verify(
+ &self,
+ sig: &[u8],
+ msg: &[u8],
+ pk: &[u8],
+ ) -> Result<(), String> {
+ let msg = parse_msg(msg)?;
+ let sig = parse_ecdsa_sig(sig)?;
+ let pk = parse_public_key(pk)?;
+ self.curve
+ .verify_ecdsa(&msg, &sig, &pk)
+ .map_err(|_| IncorrectSignature)?;
+ Ok(())
+ }
+
/// Sign a Schnorr signature.
#[wasm_bindgen(js_name = schnorrSign)]
pub fn schnorr_sign(
&self,
seckey: &[u8],
msg: &[u8],
) -> Result<Vec<u8>, String> {
let seckey = parse_secret_key(seckey)?;
let msg = parse_msg(msg)?;
let sig = self.curve.sign_schnorrabc_no_aux_rand(&msg, &seckey);
Ok(sig.as_ref().to_vec())
}
+ /// Verify a Schnorr signature.
+ #[wasm_bindgen(js_name = schnorrVerify)]
+ pub fn schnorr_verify(
+ &self,
+ sig: &[u8],
+ msg: &[u8],
+ pk: &[u8],
+ ) -> Result<(), String> {
+ let msg = parse_msg(msg)?;
+ let sig = parse_schnorr_sig(sig)?;
+ let pk = parse_public_key(pk)?;
+ self.curve
+ .verify_schnorrabc(&sig, &msg, &pk)
+ .map_err(|_| IncorrectSignature)?;
+ Ok(())
+ }
+
/// Return whether the given secret key is valid, i.e. whether is of correct
/// length (32 bytes) and is on the curve.
#[wasm_bindgen(js_name = isValidSeckey)]
pub fn is_valid_seckey(&self, seckey: &[u8]) -> bool {
parse_secret_key(seckey).is_ok()
}
/// Add a scalar to a secret key.
#[wasm_bindgen(js_name = seckeyAdd)]
pub fn seckey_add(&self, a: &[u8], b: &[u8]) -> Result<Vec<u8>, String> {
let a = parse_secret_key(a)?;
let b = parse_scalar(b)?;
Ok(a.add_tweak(&b)
.map_err(|_| InvalidSeckey)?
.secret_bytes()
.to_vec())
}
/// Add a scalar to a public key (adding G*b).
#[wasm_bindgen(js_name = pubkeyAdd)]
pub fn pubkey_add(&self, a: &[u8], b: &[u8]) -> Result<Vec<u8>, String> {
let a = parse_public_key(a)?;
let b = parse_scalar(b)?;
Ok(a.add_exp_tweak(&self.curve, &b)
.map_err(|_| InvalidPubkey)?
.serialize()
.to_vec())
}
/// Create a compact ECDSA signature (65 bytes), which allows reconstructing
/// the used public key.
/// The format is one header byte, followed by two times 32 bytes for the
/// serialized r and s values.
/// The header byte: 0x1B = first key with even y,
/// 0x1C = first key with odd y,
/// 0x1D = second key with even y,
/// 0x1E = second key with odd y,
/// add 0x04 for compressed keys.
#[wasm_bindgen(js_name = signRecoverable)]
pub fn sign_recoverable(
&self,
seckey: &[u8],
msg: &[u8],
) -> Result<Vec<u8>, String> {
let seckey = parse_secret_key(seckey)?;
let msg = parse_msg(msg)?;
let sig = self.curve.sign_ecdsa_recoverable(&msg, &seckey);
let (recover_id, sig_rs) = sig.serialize_compact();
let mut ser_sig = [0u8; 65];
ser_sig[0] = i32::from(recover_id) as u8 + 0x1b + 0x04;
ser_sig[1..].copy_from_slice(&sig_rs);
Ok(ser_sig.to_vec())
}
/// Recover the public key of a signature signed by signRecoverable.
#[wasm_bindgen(js_name = recoverSig)]
pub fn recover_sig(
&self,
sig: &[u8],
msg: &[u8],
) -> Result<Vec<u8>, String> {
let recovery_id = sig[0].saturating_sub(0x1b + 0x04);
let recovery_id = RecoveryId::try_from(recovery_id as i32)
.map_err(|_| format!("Invalid recovery ID: {recovery_id}"))?;
let sig = RecoverableSignature::from_compact(&sig[1..], recovery_id)
.map_err(|_| "Invalid signature format")?;
let msg = Message::from_digest(
msg.try_into()
.map_err(|_| format!("Invalid msg length {}", msg.len()))?,
);
let pubkey = self
.curve
.recover_ecdsa(&msg, &sig)
.map_err(|_| "Signature recovery failed")?;
Ok(pubkey.serialize().to_vec())
}
}
diff --git a/modules/ecash-lib/src/ecc.test.ts b/modules/ecash-lib/src/ecc.test.ts
index 6b03298a3..230f950ed 100644
--- a/modules/ecash-lib/src/ecc.test.ts
+++ b/modules/ecash-lib/src/ecc.test.ts
@@ -1,48 +1,68 @@
// Copyright (c) 2024 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
import { expect } from 'chai';
import { fromHex, toHex } from './io/hex.js';
import { Ecc, EccDummy } from './ecc.js';
import './initNodeJs.js';
describe('Ecc', async () => {
it('EccWasm', () => {
const ecc = new Ecc();
const sk = fromHex(
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
);
const msg = fromHex(
'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210',
);
const pk = ecc.derivePubkey(sk);
expect(toHex(pk)).to.equal(
'034646ae5047316b4230d0086c8acec687f00b1cd9d1dc634f6cb358ac0a9a8fff',
);
- expect(ecc.schnorrSign(sk, msg)).to.have.lengthOf(64);
- expect(ecc.ecdsaSign(sk, msg)).length.to.be.within(65, 73);
+ const schnorrSig = ecc.schnorrSign(sk, msg);
+ expect(schnorrSig).to.have.lengthOf(64);
+ ecc.schnorrVerify(schnorrSig, msg, pk);
+ expect(() => ecc.schnorrVerify(new Uint8Array(10), msg, pk)).to.throw(
+ 'Invalid Schnorr signature size, expected 64 bytes but got 10',
+ );
+ expect(() => ecc.schnorrVerify(new Uint8Array(64), msg, pk)).to.throw(
+ 'Incorrect signature',
+ );
+
+ const ecdsaSig = ecc.ecdsaSign(sk, msg);
+ expect(ecdsaSig).length.to.be.within(65, 73);
+ ecc.ecdsaVerify(ecdsaSig, msg, pk);
+ expect(() => ecc.ecdsaVerify(fromHex('30'), msg, pk)).to.throw(
+ 'Invalid DER signature format',
+ );
+ expect(() =>
+ ecc.ecdsaVerify(ecc.ecdsaSign(sk, new Uint8Array(32)), msg, pk),
+ ).to.throw('Incorrect signature');
+
// Round-trip recoverable signature
expect(
toHex(ecc.recoverSig(ecc.signRecoverable(sk, msg), msg)),
).to.equal(toHex(pk));
});
it('EccDummy', () => {
const dummy = new EccDummy();
expect(dummy.derivePubkey({} as any)).to.deep.equal(new Uint8Array(33));
expect(dummy.schnorrSign({} as any, {} as any)).to.deep.equal(
new Uint8Array(64),
);
+ dummy.schnorrVerify({} as any, {} as any, {} as any);
expect(dummy.ecdsaSign({} as any, {} as any)).to.deep.equal(
new Uint8Array(73),
);
+ dummy.ecdsaVerify({} as any, {} as any, {} as any);
expect(dummy.signRecoverable({} as any, {} as any)).to.deep.equal(
new Uint8Array(65),
);
expect(dummy.recoverSig({} as any, {} as any)).to.deep.equal(
new Uint8Array(33),
);
});
});
diff --git a/modules/ecash-lib/src/ecc.ts b/modules/ecash-lib/src/ecc.ts
index 42d549609..c82bf65a4 100644
--- a/modules/ecash-lib/src/ecc.ts
+++ b/modules/ecash-lib/src/ecc.ts
@@ -1,117 +1,149 @@
// Copyright (c) 2024 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
/** Interface to abstract over Elliptic Curve Cryptography */
export interface Ecc {
/** Derive a public key from secret key. */
derivePubkey(seckey: Uint8Array): Uint8Array;
/** Sign an ECDSA signature. msg needs to be a 32-byte hash */
ecdsaSign(seckey: Uint8Array, msg: Uint8Array): Uint8Array;
+ /**
+ * Verify an ECDSA signature. msg needs to be a 32-byte hash.
+ * Throws an exception if the signature is invalid.
+ **/
+ ecdsaVerify(sig: Uint8Array, msg: Uint8Array, pk: Uint8Array): void;
+
/** Sign a Schnorr signature. msg needs to be a 32-byte hash */
schnorrSign(seckey: Uint8Array, msg: Uint8Array): Uint8Array;
+ /**
+ * Verify a Schnorr signature. msg needs to be a 32-byte hash.
+ * Throws an exception if the signature is invalid.
+ **/
+ schnorrVerify(sig: Uint8Array, msg: Uint8Array, pk: Uint8Array): void;
+
/**
* Return whether the given secret key is valid, i.e. whether is of correct
* length (32 bytes) and is on the curve.
*/
isValidSeckey(seckey: Uint8Array): boolean;
/** Add a scalar to a secret key */
seckeyAdd(a: Uint8Array, b: Uint8Array): Uint8Array;
/** Add a scalar to a public key (adding G*b) */
pubkeyAdd(a: Uint8Array, b: Uint8Array): Uint8Array;
/** Sign a ECDSA recoverable signature, includes the recovery ID */
signRecoverable(seckey: Uint8Array, msg: Uint8Array): Uint8Array;
/** Recover the public key of an ECDSA signed signature (with recovery ID) */
recoverSig(sig: Uint8Array, msg: Uint8Array): Uint8Array;
}
/** Dummy Ecc impl that always returns 0, useful for measuring tx size */
export class EccDummy implements Ecc {
derivePubkey(_seckey: Uint8Array): Uint8Array {
return new Uint8Array(33);
}
ecdsaSign(_seckey: Uint8Array, _msg: Uint8Array): Uint8Array {
return new Uint8Array(73);
}
+ ecdsaVerify(_sig: Uint8Array, _msg: Uint8Array, _pk: Uint8Array): void {}
+
schnorrSign(_seckey: Uint8Array, _msg: Uint8Array): Uint8Array {
return new Uint8Array(64);
}
+ schnorrVerify(_sig: Uint8Array, _msg: Uint8Array, _pk: Uint8Array): void {}
+
isValidSeckey(_seckey: Uint8Array): boolean {
return false;
}
seckeyAdd(_a: Uint8Array, _b: Uint8Array): Uint8Array {
return new Uint8Array(32);
}
pubkeyAdd(_a: Uint8Array, _b: Uint8Array): Uint8Array {
return new Uint8Array(32);
}
signRecoverable(_seckey: Uint8Array, _msg: Uint8Array): Uint8Array {
return new Uint8Array(65);
}
recoverSig(_sig: Uint8Array, _msg: Uint8Array): Uint8Array {
return new Uint8Array(33);
}
}
const ECC: { ecc?: Ecc } = {};
export function __setEcc(ecc: Ecc) {
ECC.ecc = ecc;
}
export class Ecc implements Ecc {
/** Derive a public key from secret key. */
derivePubkey(seckey: Uint8Array): Uint8Array {
return ECC.ecc!.derivePubkey(seckey);
}
/** Sign an ECDSA signature. msg needs to be a 32-byte hash */
ecdsaSign(seckey: Uint8Array, msg: Uint8Array): Uint8Array {
return ECC.ecc!.ecdsaSign(seckey, msg);
}
+ /**
+ * Verify an ECDSA signature. msg needs to be a 32-byte hash.
+ * Throws an exception if the signature is invalid.
+ **/
+ ecdsaVerify(sig: Uint8Array, msg: Uint8Array, pk: Uint8Array): void {
+ ECC.ecc?.ecdsaVerify(sig, msg, pk);
+ }
+
/** Sign a Schnorr signature. msg needs to be a 32-byte hash */
schnorrSign(seckey: Uint8Array, msg: Uint8Array): Uint8Array {
return ECC.ecc!.schnorrSign(seckey, msg);
}
+ /**
+ * Verify a Schnorr signature. msg needs to be a 32-byte hash.
+ * Throws an exception if the signature is invalid.
+ **/
+ schnorrVerify(sig: Uint8Array, msg: Uint8Array, pk: Uint8Array): void {
+ ECC.ecc?.schnorrVerify(sig, msg, pk);
+ }
+
/**
* Return whether the given secret key is valid, i.e. whether is of correct
* length (32 bytes) and is on the curve.
*/
isValidSeckey(seckey: Uint8Array): boolean {
return ECC.ecc!.isValidSeckey(seckey);
}
/** Add a scalar to a secret key */
seckeyAdd(a: Uint8Array, b: Uint8Array): Uint8Array {
return ECC.ecc!.seckeyAdd(a, b);
}
/** Add a scalar to a public key (adding G*b) */
pubkeyAdd(a: Uint8Array, b: Uint8Array): Uint8Array {
return ECC.ecc!.pubkeyAdd(a, b);
}
signRecoverable(seckey: Uint8Array, msg: Uint8Array): Uint8Array {
return ECC.ecc!.signRecoverable(seckey, msg);
}
recoverSig(sig: Uint8Array, msg: Uint8Array): Uint8Array {
return ECC.ecc!.recoverSig(sig, msg);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Wed, May 21, 17:59 (1 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5865641
Default Alt Text
(16 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment