mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 09:51:39 +00:00
84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
import { hmac } from '@noble/hashes/hmac';
|
|
import { keccak_256 } from '@noble/hashes/sha3';
|
|
import { sha256 } from '@noble/hashes/sha256';
|
|
import * as secp256k1 from '@noble/secp256k1';
|
|
import { utf8 } from '@scure/base';
|
|
import type { CosmosTxV1beta1SignDoc as SignDoc } from '@sonr.io/es/protobufs';
|
|
import type { StdSignDoc } from '@sonr.io/es/registry';
|
|
|
|
import { serialiseSignDoc } from './serialise';
|
|
|
|
function sign(
|
|
bytes: Uint8Array,
|
|
privateKey: Uint8Array,
|
|
type: 'secp256k1' | 'ethsecp256k1'
|
|
): Uint8Array {
|
|
// Required polyfills for secp256k1 that must be called before any sign ops.
|
|
// See: https://github.com/paulmillr/noble-secp256k1?tab=readme-ov-file#usage
|
|
secp256k1.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp256k1.etc.concatBytes(...m));
|
|
const hash = type === 'secp256k1' ? sha256(bytes) : keccak_256(bytes);
|
|
return secp256k1.sign(hash, privateKey).toCompactRawBytes();
|
|
}
|
|
|
|
/**
|
|
* Signs the given amino-encoded `stdSignDoc` with the given `privateKey` using
|
|
* secp256k1, and returns the signature bytes. For Injective, the `type` param
|
|
* must be set to `ethsecp256k1`.
|
|
*/
|
|
export function signAmino(
|
|
stdSignDoc: StdSignDoc,
|
|
privateKey: Uint8Array,
|
|
type: 'secp256k1' | 'ethsecp256k1' = 'secp256k1'
|
|
): Uint8Array {
|
|
return sign(serialiseSignDoc(stdSignDoc), privateKey, type);
|
|
}
|
|
|
|
/**
|
|
* Signs the given proto-encoded `signDoc` with the given `privateKey` using
|
|
* secp256k1, and returns the signature bytes. For Injective, the `type` param
|
|
* must be set to `ethsecp256k1`.
|
|
*/
|
|
export function signDirect(
|
|
signDoc: SignDoc,
|
|
privateKey: Uint8Array,
|
|
type: 'secp256k1' | 'ethsecp256k1' = 'secp256k1'
|
|
): Uint8Array {
|
|
return sign(signDoc.toBinary(), privateKey, type);
|
|
}
|
|
|
|
/**
|
|
* Hashes and returns the digest of the given EIP191 `message` bytes.
|
|
*/
|
|
export function hashEthArbitraryMessage(message: Uint8Array): Uint8Array {
|
|
return keccak_256(
|
|
Uint8Array.from([
|
|
...utf8.decode('\x19Ethereum Signed Message:\n'),
|
|
...utf8.decode(message.length.toString()),
|
|
...message,
|
|
])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Recovers and returns the secp256k1 public key of the signer given the arbitrary
|
|
* `message` and `signature` that was signed using EIP191.
|
|
*/
|
|
export function recoverPubKeyFromEthSignature(
|
|
message: Uint8Array,
|
|
signature: Uint8Array
|
|
): Uint8Array {
|
|
if (signature.length !== 65) {
|
|
throw new Error('Invalid signature');
|
|
}
|
|
const r = signature.slice(0, 32);
|
|
const s = signature.slice(32, 64);
|
|
const v = signature[64];
|
|
// Adapted from https://github.com/ethers-io/ethers.js/blob/6017d3d39a4d428793bddae33d82fd814cacd878/src.ts/crypto/signature.ts#L255-L265
|
|
const yParity = v <= 1 ? v : (v + 1) % 2;
|
|
const secpSignature = secp256k1.Signature.fromCompact(
|
|
Uint8Array.from([...r, ...s])
|
|
).addRecoveryBit(yParity);
|
|
const digest = hashEthArbitraryMessage(message);
|
|
return secpSignature.recoverPublicKey(digest).toRawBytes(true);
|
|
}
|