Skip to content

Commit

Permalink
added new signature encoding fix + wallet fromWIF fix and fee estimat…
Browse files Browse the repository at this point in the history
…e fallbacks
  • Loading branch information
cf committed Aug 14, 2024
1 parent 7dc8691 commit bcf9986
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 3 deletions.
78 changes: 77 additions & 1 deletion src/ecc/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,31 @@ function verifyNormalizeSecp256K1Signature(signatureHex: string, messageHashHex:
function bigIntU256ToBytesBE(u256: bigint): Uint8Array {
return hexToU8Array(u256.toString(16).padStart(64, '0'));
}
function isValidDERSignatureEncodingInternal(sig: Uint8Array | number[]): number {
if(sig.length < 9) return 1;
if(sig.length > 73) return 2;
if(sig[0] != 0x30) return 3;
if(sig[1] != sig.length - 3) return 4;
let lenR = sig[3];
if(5 + lenR >= sig.length) return 5;
let lenS = sig[5 + lenR];
if(lenR + lenS + 7 != sig.length) return 6;
if(sig[2] != 0x02) return 7;
if(lenR == 0) return 8;
if(sig[4] & 0x80) return 9;
if(lenR > 1 && sig[4] == 0x00 && !(sig[5] & 0x80)) return 10;
if(sig[lenR + 4] != 0x02) return 11;
if(lenS == 0) return 12;
if(sig[lenR + 6] & 0x80) return 13;
if (lenS > 1 && (sig[lenR + 6] == 0x00) && !(sig[lenR + 7] & 0x80)) return 14;
return 0;
}


function isValidDERSignatureEncoding(sig: Uint8Array | number[]): boolean {
return isValidDERSignatureEncodingInternal(sig) === 0;
}
/*
function writeDerEncodedU256Bytes(xBytesBE: Uint8Array, dest: Uint8Array, destOffset: number): number {
const hasExtraPadByte = xBytesBE[0] >= 0x80;
const offset = hasExtraPadByte ? 1 : 0;
Expand All @@ -67,7 +91,7 @@ function writeDerEncodedU256Bytes(xBytesBE: Uint8Array, dest: Uint8Array, destOf
function getSignatureLengthForRS(r: Uint8Array, s: Uint8Array): number {
return 6 + r.length + s.length + (r[0] >= 0x80 ? 1 : 0) + (s[0] >= 0x80 ? 1 : 0);
}
function derEncodeSignature(r: Uint8Array, s: Uint8Array): Uint8Array {
function derEncodeSignatureOld(r: Uint8Array, s: Uint8Array): Uint8Array {
const length = getSignatureLengthForRS(r, s);
const output = new Uint8Array(length);
output[0] = 0x30;
Expand All @@ -76,7 +100,57 @@ function derEncodeSignature(r: Uint8Array, s: Uint8Array): Uint8Array {
offset += writeDerEncodedU256Bytes(r, output, offset);
offset += writeDerEncodedU256Bytes(s, output, offset);
return output;
}*/


/*
static int secp256k1_ecdsa_sig_serialize(unsigned char *sig, size_t *size, const secp256k1_scalar* ar, const secp256k1_scalar* as) {
unsigned char r[33] = {0}, s[33] = {0};
unsigned char *rp = r, *sp = s;
size_t lenR = 33, lenS = 33;
secp256k1_scalar_get_b32(&r[1], ar);
secp256k1_scalar_get_b32(&s[1], as);
while (lenR > 1 && rp[0] == 0 && rp[1] < 0x80) { lenR--; rp++; }
while (lenS > 1 && sp[0] == 0 && sp[1] < 0x80) { lenS--; sp++; }
if (*size < 6+lenS+lenR) {
*size = 6 + lenS + lenR;
return 0;
}
*size = 6 + lenS + lenR;
sig[0] = 0x30;
sig[1] = 4 + lenS + lenR;
sig[2] = 0x02;
sig[3] = lenR;
memcpy(sig+4, rp, lenR);
sig[4+lenR] = 0x02;
sig[5+lenR] = lenS;
memcpy(sig+lenR+6, sp, lenS);
return 1;
}*/
function derEncodeSignature(ar: Uint8Array, as: Uint8Array): Uint8Array {
const r = new Uint8Array(33);
const s = new Uint8Array(33);
let lenR = 33;
let lenS = 33;
r.set(ar, 1);
s.set(as, 1);
let rp = 0;
let sp = 0;
while (lenR > 1 && r[rp] === 0 && r[rp+1] < 0x80) { lenR--; rp++; }
while (lenS > 1 && s[sp] === 0 && s[sp+1] < 0x80) { lenS--; sp++; }
const length = 6 + lenS + lenR;
const output = new Uint8Array(length);
output[0] = 0x30;
output[1] = 4 + lenS + lenR;
output[2] = 0x02;
output[3] = lenR;
output.set(r.subarray(rp, rp+lenR), 4);
output[4+lenR] = 0x02;
output[5+lenR] = lenS;
output.set(s.subarray(sp, sp+lenS), lenR+6);
return output;
}

function derEncodeBigIntSignature(r: bigint, s: bigint): Uint8Array {
return derEncodeSignature(bigIntU256ToBytesBE(r), bigIntU256ToBytesBE(s));
}
Expand All @@ -103,4 +177,6 @@ export {
normalizeSignatureFromDer,
verifyNormalizeSecp256K1Signature,
verifyNormailzedSignature,
isValidDERSignatureEncoding,
isValidDERSignatureEncodingInternal,
}
15 changes: 15 additions & 0 deletions src/rpc/linkElectrs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ class DogeLinkElectrsRPC implements IDogeLinkElectrsRPC {
this.httpClient = httpClient || new FetchHTTPClient();
this.electrsURL = trimTrailingSlash(electrsURL);
}
estimateSmartFeeOrFallback(target: number, fallbackFeeRate: number): Promise<number> {
return this.estimateSmartFee(target).catch(() => fallbackFeeRate);
}
async getFeeEstimateMapOrFallback(fallbackFeeRate: number): Promise<IFeeEstimateMap> {
try {
const feeMap = await this.getFeeEstimateMap();
return feeMap;
}catch(e){
const fallback: any = {};
for(let i=1; i<=25; i++){
fallback[i+''] = fallbackFeeRate;
}
return fallback;
}
}
getFeeEstimateMap(): Promise<IFeeEstimateMap> {
return this.getJSONElectrs<IFeeEstimateMap>('/fee-estimates');
}
Expand Down
15 changes: 15 additions & 0 deletions src/rpc/linkElectrsCombo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ class DogeLinkElectrsComboRPC implements IDogeLinkElectrsRPC {
);
this.electrsRPC = electrsRPC;
}
estimateSmartFeeOrFallback(target: number, fallbackFeeRate: number): Promise<number> {
return this.estimateSmartFee(target).catch(() => fallbackFeeRate);
}
async getFeeEstimateMapOrFallback(fallbackFeeRate: number): Promise<IFeeEstimateMap> {
try {
const feeMap = await this.getFeeEstimateMap();
return feeMap;
}catch(e){
const fallback: any = {};
for(let i=1; i<=25; i++){
fallback[i+''] = fallbackFeeRate;
}
return fallback;
}
}
getTransactionWithStatus(txid: string): Promise<ITransactionWithStatus> {
return this.electrsRPC.getTransactionWithStatus(txid);
}
Expand Down
15 changes: 15 additions & 0 deletions src/rpc/linkRPC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ class DogeLinkRPC implements IDogeLinkRPC {
}
this.httpClient = httpClient || (new FetchHTTPClient());
}
estimateSmartFeeOrFallback(target: number, fallbackFeeRate: number): Promise<number> {
return this.estimateSmartFee(target).catch(() => fallbackFeeRate);
}
async getFeeEstimateMapOrFallback(fallbackFeeRate: number): Promise<IFeeEstimateMap> {
try {
const feeMap = await this.getFeeEstimateMap();
return feeMap;
}catch(e){
const fallback: any = {};
for(let i=1; i<=25; i++){
fallback[i+''] = fallbackFeeRate;
}
return fallback;
}
}
async getTransactionWithStatus(txid: string): Promise<ITransactionWithStatus> {
const response = await this.command<IDogeRPCGetRawTxResponse>("getrawtransaction", [txid, 1]);
const tx = Transaction.fromHex(response.hex);
Expand Down
3 changes: 3 additions & 0 deletions src/rpc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ interface IDogeLinkRPC {
resolveBlockNumber(blockHashOrNumber: string | number): Promise<number>;
getFeeEstimateMap(): Promise<IFeeEstimateMap>;
estimateSmartFee(target: number): Promise<number>;
estimateSmartFeeOrFallback(target: number, fallbackFeeRate: number): Promise<number>;
getFeeEstimateMapOrFallback(fallbackFeeRate: number): Promise<IFeeEstimateMap>;

waitForTransaction(txid: string, waitUntilConfirmed?: boolean, pollInterval?: number, maxAttempts?: number): Promise<ITransactionWithStatus>;
}
interface IFeeEstimateMap {
Expand Down
10 changes: 9 additions & 1 deletion src/utils/random.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ function randomBytesFactory(): (length: number) => Uint8Array {

const cryptoRandomBytes = randomBytesFactory();

function insecurePseudoRandomBytes(length: number): Uint8Array {
const array = new Uint8Array(length);
for(let i=0; i<length; i++){
array[i] = Math.floor(Math.random()*256);
}
return array;
}
export {
cryptoRandomBytes,
}
insecurePseudoRandomBytes,
}
2 changes: 1 addition & 1 deletion src/wallet/memory/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class DogeMemoryWallet implements IDogeTransactionSigner{

static fromWIF(wif: string, networkId?: DogeNetworkId, name?: string){
const { privateKey, networkId: networkIdFromWIF } = decodePrivateKeyAndNetworkFromWIF(wif, networkId);
return new DogeMemoryWallet(privateKey, networkIdFromWIF, name);
return new DogeMemoryWallet(privateKey, networkId || networkIdFromWIF, name);
}
getWalletForOtherNetwork(networkId: DogeNetworkId){
return new DogeMemoryWallet(this.privateKey, networkId, this.name);
Expand Down
19 changes: 19 additions & 0 deletions test/signature.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DogeMemoryWallet, DogeMemoryWalletProvider, hexToU8Array, ISignatureResult, isValidDERSignatureEncoding, isValidDERSignatureEncodingInternal, u8ArrayToHex } from "../src";
import { seq } from "../src/utils/misc";
import { insecurePseudoRandomBytes } from "../src/utils/random";

describe('Signatures', () => {
// test wallets for all supported networks
const randomSignatures: ISignatureResult[] = [];
beforeAll(async () => {
const sigs = await Promise.all(seq(1000).map(x=>DogeMemoryWallet.generateRandom("dogeTestnet").signHash(u8ArrayToHex(insecurePseudoRandomBytes(32)))));
sigs.forEach(s=>randomSignatures.push(s));
});

it('check random signature serialization', () => {
randomSignatures.forEach(sig=>{
const sigBytes = hexToU8Array(sig.signature+"01");
expect(isValidDERSignatureEncodingInternal(sigBytes)).toBe(0);
})
});
});

0 comments on commit bcf9986

Please sign in to comment.