Skip to content

Commit

Permalink
Fix Secp256k1 Signature in typescript to be 65-bytes recoverable form…
Browse files Browse the repository at this point in the history
…at (#4776)
  • Loading branch information
joyqvq committed Sep 24, 2022
1 parent d21f6a1 commit 8066d2a
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 23 deletions.
33 changes: 19 additions & 14 deletions sdk/typescript/src/cryptography/secp256k1-keypair.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import * as secp from "@noble/secp256k1";
import * as secp from '@noble/secp256k1';
import { Base64DataBuffer } from '../serialization/base64';
import { Keypair } from './keypair';
import { PublicKey, SignatureScheme } from './publickey';
import { hmac } from '@noble/hashes/hmac';
import { sha256 } from "@noble/hashes/sha256";
import { Secp256k1PublicKey } from "./secp256k1-publickey";
import { sha256 } from '@noble/hashes/sha256';
import { Secp256k1PublicKey } from './secp256k1-publickey';
import { Signature } from '@noble/secp256k1';

secp.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => {
const h = hmac.create(sha256, key);
msgs.forEach(msg => h.update(msg));
msgs.forEach((msg) => h.update(msg));
return h.digest();
};

Expand Down Expand Up @@ -42,10 +43,10 @@ export class Secp256k1Keypair implements Keypair {
const secretKey: Uint8Array = secp.utils.randomPrivateKey();
const publicKey: Uint8Array = secp.getPublicKey(secretKey, true);

this.keypair = {publicKey, secretKey};
this.keypair = { publicKey, secretKey };
}
}

/**
* Get the key scheme of the keypair Secp256k1
*/
Expand All @@ -60,7 +61,7 @@ export class Secp256k1Keypair implements Keypair {
const secretKey = secp.utils.randomPrivateKey();
const publicKey = secp.getPublicKey(secretKey, true);

return new Secp256k1Keypair({publicKey, secretKey});
return new Secp256k1Keypair({ publicKey, secretKey });
}

/**
Expand All @@ -76,7 +77,6 @@ export class Secp256k1Keypair implements Keypair {
* @param options: skip secret key validation
*/


static fromSecretKey(
secretKey: Uint8Array,
options?: { skipValidation?: boolean }
Expand All @@ -87,11 +87,11 @@ export class Secp256k1Keypair implements Keypair {
const signData = encoder.encode('sui validation');
const msgHash = sha256(signData);
const signature = secp.signSync(msgHash, secretKey);
if (!secp.verify(signature, msgHash, publicKey)) {
if (!secp.verify(signature, msgHash, publicKey, { strict: true })) {
throw new Error('Provided secretKey is invalid');
}
}
return new Secp256k1Keypair({publicKey, secretKey});
return new Secp256k1Keypair({ publicKey, secretKey });
}

/**
Expand All @@ -101,7 +101,7 @@ export class Secp256k1Keypair implements Keypair {
*/
static fromSeed(seed: Uint8Array): Secp256k1Keypair {
let publicKey = secp.getPublicKey(seed, true);
return new Secp256k1Keypair({publicKey, secretKey: seed});
return new Secp256k1Keypair({ publicKey, secretKey: seed });
}

/**
Expand All @@ -116,8 +116,13 @@ export class Secp256k1Keypair implements Keypair {
*/
signData(data: Base64DataBuffer): Base64DataBuffer {
const msgHash = sha256(data.getData());
return new Base64DataBuffer(
secp.signSync(msgHash, this.keypair.secretKey)
);
const [sig, rec_id] = secp.signSync(msgHash, this.keypair.secretKey, {
canonical: true,
recovered: true,
});
var recoverable_sig = new Uint8Array(65);
recoverable_sig.set(Signature.fromDER(sig).toCompactRawBytes());
recoverable_sig.set([rec_id], 64);
return new Base64DataBuffer(recoverable_sig);
}
}
14 changes: 9 additions & 5 deletions sdk/typescript/test/unit/cryptography/secp256k1-keypair.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { Base64DataBuffer, Secp256k1Keypair } from '../../../src';
import { describe, it, expect } from 'vitest';
import * as secp from '@noble/secp256k1';
import { Signature } from '@noble/secp256k1';

// Test case from https://github.com/rust-bitcoin/rust-secp256k1/blob/master/examples/sign_verify.rs#L26
const VALID_SECP256K1_SECRET_KEY = [
Expand Down Expand Up @@ -64,12 +65,15 @@ describe('secp256k1-keypair', () => {
);

const msgHash = await secp.utils.sha256(signData.getData());
const signature = keypair.signData(signData);
const isValid = secp.verify(
signature.getData(),
const sig = keypair.signData(signData);
const pubkey = secp.recoverPublicKey(
msgHash,
keypair.getPublicKey().toBytes()
Signature.fromCompact(sig.getData().slice(0, 64)),
sig.getData()[64],
true
);
expect(Buffer.from(pubkey).toString('base64')).toEqual(
keypair.getPublicKey().toBase64()
);
expect(isValid).toBeTruthy();
});
});
12 changes: 8 additions & 4 deletions sdk/typescript/test/unit/signers/raw-signer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Secp256k1Keypair,
} from '../../../src';
import * as secp from '@noble/secp256k1';
import { Signature } from '@noble/secp256k1';

describe('RawSigner', () => {
it('Ed25519 keypair signData', async () => {
Expand All @@ -35,11 +36,14 @@ describe('RawSigner', () => {
const msgHash = await secp.utils.sha256(signData.getData());
const signer = new RawSigner(keypair);
const { signature, pubKey } = await signer.signData(signData);
const isSecpValid = secp.verify(
signature.getData(),
const recovered_pubkey = secp.recoverPublicKey(
msgHash,
pubKey.toBytes()
Signature.fromCompact(signature.getData().slice(0, 64)),
signature.getData()[64],
true
);
expect(isSecpValid).toBeTruthy();
const expected = keypair.getPublicKey().toBase64();
expect(pubKey.toBase64()).toEqual(expected);
expect(Buffer.from(recovered_pubkey).toString('base64')).toEqual(expected);
});
});

0 comments on commit 8066d2a

Please sign in to comment.