Table of Contents
Question content
Project Overview
data
script
My attempt
Workaround
Home Backend Development Golang Unable to verify Go generated ECDSA signature using JS

Unable to verify Go generated ECDSA signature using JS

Feb 10, 2024 pm 08:03 PM
go language Blockchain mobile application

无法使用 JS 验证 Go 生成的 ECDSA 签名

php editor Xiaoxin encountered a problem when using Go language to generate ECDSA signatures, that is, JS could not be used for verification. The solution to this problem is to add some additional fields to the Go code to ensure the correctness of the signature. By making some modifications to the Go code, we can solve this problem and enable JS to correctly verify the ECDSA signature generated by Go. This article will introduce you to the specific solutions and steps in detail.

Question content

I ran into a small problem, (my assumption is that there is a small thing holding me back, but I don't know what), as stated in the title.

I'll start by outlining what I'm doing and then provide everything I have.

Project Overview

I'm hashing the file using SHA-256 in the mobile app and signing the hash on the backend using the ECDSA P-256 key. And then this goes on and on. If the user wants, he can verify the integrity of the file by hashing the file again and looking up the hash and getting the hash, some metadata, and the signature.

In order to verify that the data was submitted to my application and not a third party (the hashes remain in the blockchain, but that is not important for this problem), the application will try to verify the signature using the public key. This work is good.

Now I want to add this option to my website as well, but the problem is. If I use the jsrsasign or webcrypto api, my signature is invalid.

data

  • Signature example: 3045022100f28c29042a6d766810e21f2c0a1839f93140989299cae1d37b49a454373659c802203d0967be0696686414fe2efed3a71bc1639d066 ee1 27cfb7c0ad369521459d00
  • Public key:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZ
c6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==
-----END PUBLIC KEY-----
Copy after login
  • Hash value: bb5dbfcb5206282627254ab23397cda842b082696466f2563503f79a5dccf942

script

JS code
<code>const validHash = document.getElementById("valid-hash");
const locationEmbedded = document.getElementById("location-embedded")
const signatureValid = document.getElementById("valid-sig")
const fileSelector = document.getElementById('file-upload');
const mcaptchaToken = document.getElementById("mcaptcha__token")
const submission = document.getElementById("submission")
let publicKey;

fileSelector.addEventListener("change", (event) => {
    document.getElementsByClassName("file-upload-label")[0].innerHTML = event.target.files[0].name
})
submission.addEventListener('click', async (event) => {
    let token = mcaptchaToken.value
    if (token == null || token == "") {
        alert("Please activate the Captcha!")
        return
    }
    const fileList = fileSelector.files;
    if (fileList[0]) {
        const file = fileList[0]
        const fileSize = file.size;
        let fileData = await readBinaryFile(file)
        let byteArray = new Uint8Array(fileData);
        const bytes = await hashFile(byteArray)
        try {
            let resp = await callApi(toHex(bytes), token)
            validHash.innerHTML = "\u2713"

            const mediainfo = await MediaInfo({ format: 'object' }, async (mediaInfo) => { // Taken from docs
                mediaInfo.analyzeData(() => file.size, (chunkSize, offset) => {
                    return new Promise((resolve, reject) => {
                        const reader = new FileReader()
                        reader.onload = (event) => {
                            if (event.target.error) {
                                reject(event.target.error)
                            }
                            resolve(new Uint8Array(event.target.result))
                        }
                        reader.readAsArrayBuffer(file.slice(offset, offset + chunkSize))
                    })
                })
                try {
                    let tags = mediaInfo.media.track[0].extra
                    latitude = tags.LATITUDE
                    longitude = tags.LONGITUDE
                    if (latitude && longitude) {
                        locationEmbedded.innerHTML = "\u2713"
                    } else {
                        locationEmbedded.innerHTML = "\u2717"
                    }
                } catch (e) {
                    locationEmbedded.innerHTML = "\u2717"
                }
               
            })
            if (publicKey == undefined) {
                let req = await fetch("/publickey")
                if (req.ok) {
                    publicKey = await req.text()
                } else {
                    throw "Could not get public key"
                }
            }

            let signature = resp.data.comment
            if (signature == null || signature == "") {
                throw "No signature found"
            }
            //const timeStamps = resp.data.timestamps
            const hashString = resp.data.hash_string
            console.log(hashString)
            if (hashString !== toHex(bytes)) {
                validHash.innerHTML = "\u2717"
            } else {
                validHash.innerHTML = "\u2713"
            }
            const result = await validateSignature(publicKey, signature, hashString)
            console.log("Valid signature: " + result)
            if (result) {
                signatureValid.innerHTML = "\u2713"
            } else {
                signatureValid.innerHTML = "\u2717"
            }
            mcaptchaToken.value = ""
        } catch (e) {
            alert("Error: " + e)
            window.location.reload()
        }

    } else {
        alert("No file selected");
    }
});


function toHex(buffer) {
    return Array.prototype.map.call(buffer, x => ('00' + x.toString(16)).slice(-2)).join('');
}

async function callApi(hash, token) {
    const url = "/verify";
    let resp = await fetch(url, {
        headers: { "X-MCAPTCHA-TOKEN": token },
        method: "POST",
        body: JSON.stringify({ hash: hash })
    })
    if (resp.ok) {
        return await resp.json();
    } else {
        if (resp.status == 401) {
            throw resp.status
        } else {
            console.log(resp)
            throw "Your hash is either invalid or has not been submitted via the Decentproof App!"
        }
    }
}

async function hashFile(byteArray) {
    let hashBytes = await window.crypto.subtle.digest('SHA-256', byteArray);
    return new Uint8Array(hashBytes)
}

async function validateSignature(key, signature,hashData) {
    const importedKey = importPublicKey(key)
    const sig = new KJUR.crypto.Signature({"alg": "SHA256withECDSA"});
    sig.init(importedKey)
    sig.updateHex(hashData);
    return sig.verify(signature)
}

function readBinaryFile(file) {
    return new Promise((resolve, reject) => {
        var fr = new FileReader();
        fr.onload = () => {
            resolve(fr.result)
        };
        fr.readAsArrayBuffer(file);
    });
}

function importPublicKey(pem) {
    console.log(pem)
    return KEYUTIL.getKey(pem); 
  }

  function hexToBytes(hex) {
    for (var bytes = [], c = 0; c < hex.length; c += 2)
    bytes.push(parseInt(hex.substr(c, 2), 16));
    return new Uint8Array(bytes);
}
</code>
Copy after login
Application verification code (Flutter Dart)
<code>import 'dart:convert';
import 'package:convert/convert.dart';
import 'dart:typed_data';
import 'package:basic_utils/basic_utils.dart';
import 'package:decentproof/features/verification/interfaces/ISignatureVerifcationService.dart';
import 'package:pointycastle/asn1/asn1_parser.dart';
import 'package:pointycastle/asn1/primitives/asn1_integer.dart';
import 'package:pointycastle/signers/ecdsa_signer.dart';

class SignatureVerificationService implements ISignatureVerificationService {
  late final ECPublicKey pubKey;
  SignatureVerificationService() {
    pubKey = loadAndPrepPubKey();
  }

  final String pemPubKey = """
-----BEGIN EC PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZ
c6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==
-----END EC PUBLIC KEY-----
""";

  ECSignature loadAndConvertSignature(String sig) {
    //Based on: https://github.com/bcgit/pc-dart/issues/159#issuecomment-1105689978
    Uint8List bytes = Uint8List.fromList(hex.decode(sig));
    ASN1Parser p = ASN1Parser(bytes);
    //Needs to be dynamic or otherwise throws odd errors
    final seq = p.nextObject() as dynamic;
    ASN1Integer ar = seq.elements?[0] as ASN1Integer;
    ASN1Integer as = seq.elements?[1] as ASN1Integer;
    BigInt r = ar.integer!;
    BigInt s = as.integer!;
    return ECSignature(r, s);
  }

  ECPublicKey loadAndPrepPubKey() {
    return CryptoUtils.ecPublicKeyFromPem(pemPubKey);
  }

  @override
  bool verify(String hash, String sig) {
    ECSignature convertedSig = loadAndConvertSignature(sig);
    final ECDSASigner signer = ECDSASigner();
    signer.init(false, PublicKeyParameter<ECPublicKey>(loadAndPrepPubKey()));
    Uint8List messageAsBytes = Uint8List.fromList(utf8.encode(hash));
    return signer.verifySignature(messageAsBytes, convertedSig);
  }
}
</code>
Copy after login
Key generation script (Go)
<code>package main

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/x509"
    "encoding/pem"
    "flag"
    "fmt"
    "os"
)

func main() {
    var outPutDir string
    var outPutFileName string
    flag.StringVar(&outPutDir, "out", "./", "Output directory")
    flag.StringVar(&outPutFileName, "name", "key", "Output file name e.g key, my_project_key etc. Adding .pem is not needed")
    flag.Parse()
    key, err := generateKeys()
    if err != nil {
        fmt.Printf("Something went wrong %d", err)
        return
    }
    err = saveKeys(key, outPutDir, outPutFileName)
    if err != nil {
        fmt.Printf("Something went wrong %d", err)
        return
    }
    fmt.Printf("Keys generated and saved to %s%s.pem and %spub_%s.pem", outPutDir, outPutFileName, outPutDir, outPutFileName)

}

func generateKeys() (*ecdsa.PrivateKey, error) {
    return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}
func saveKeys(key *ecdsa.PrivateKey, outPutDir string, outPutFileName string) error {
    bytes, err := x509.MarshalECPrivateKey(key)
    if err != nil {
        return err
    }
    privBloc := pem.Block{Type: "EC PRIVATE KEY", Bytes: bytes}
    privKeyFile, err := os.Create(outPutDir + outPutFileName + ".pem")
    if err != nil {
        return err
    }
    defer privKeyFile.Close()
    err = pem.Encode(privKeyFile, &privBloc)
    if err != nil {
        return err
    }

    bytes, err = x509.MarshalPKIXPublicKey(&key.PublicKey)
    pubBloc := pem.Block{Type: "EC Public KEY", Bytes: bytes}
    pubKeyFile, err := os.Create(outPutDir + "pub_" + outPutFileName + ".pem")
    if err != nil {
        return err
    }
    defer pubKeyFile.Close()
    err = pem.Encode(pubKeyFile, &pubBloc)
    if err != nil {
        return err
    }

    return nil
}
</code>
Copy after login

Link to signature wrapper script: link

My attempt

  • I've tested using two new key pairs (and your library) to sign some sample data to see if what's in the key is wrong, and it's not the case
  • I have tested the signed data using your library and my private key and verified it with my public key to see if my private key is corrupt and it is not the case
  • I have tried all operations of the Network Encryption API without success
  • I tried loading the ECDSA public key and using new KJUR.crypto.ECDSA({"curve":"secp256r1"}).verifyHex(hash,signature,pubKeyHex) with With the above data, it doesn't work (only tested in browser console)
  • I used Firefox and Safari to see if there were any differences, but nothing changed
  • I tried passing the hash value as a string via sig.updateString(hashData) without success
  • There are some other smaller changes
  • Comparing the hashes, r&s signatures on the website and the app website, everything is as expected.
  • I have tracked the entire process from front-end to back-end and no data has changed

My last attempt was the fourth because at least from my understanding if you use the regular way (which I did in the script above) your data will be hashed, as far as I'm concerned In terms of this, it's the opposite of productive, because I've already got the hash value, so if it's hashed twice, of course, it won't match. But for reasons I don't understand, I still get false as the return value.

One last thought, if using P-256 signature, could the problem be that the go ecdsa library truncates the message to 32 bytes? Maybe not so in JS?

Workaround

Validation in JavaScript code is incompatible with Dart code for two reasons:

  • 首先,JavaScript代码使用KJUR.crypto.Signature (),它隐式对数据进行哈希处理。由于数据已经被散列,这会导致双重散列。在 Dart 方面,不会发生隐式哈希(因为 ECDSASigner())。
    为了避免 JavaScript 端的隐式哈希并与 Dart 代码兼容,KJUR.crypto.ECDSA() 可以用来代替 KJUR.crypto.Signature()
  • 其次,JavaScript 代码中的 updateHex() 对十六进制编码的哈希值执行十六进制解码,而在 Dart 代码中,十六进制编码的哈希值是 UTF-8 编码的。
    为了与 Dart 代码兼容,十六进制编码的哈希值在 JavaScript 代码中也必须采用 UTF-8 编码。

以下 JavaScript 代码解决了这两个问题:

(async () => {

var spki = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZ
c6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==
-----END PUBLIC KEY-----`; 
var pubkey = KEYUTIL.getKey(spki).getPublicKeyXYHex()
var pubkeyHex = '04' + pubkey.x + pubkey.y

var msgHashHex = ArrayBuffertohex(new TextEncoder().encode("bb5dbfcb5206282627254ab23397cda842b082696466f2563503f79a5dccf942").buffer)
// var msgHashHex = ArrayBuffertohex(new TextEncoder().encode("bb5dbfcb5206282627254ab23397cda8").buffer); // works also since only the first 32 bytes are considered for P-256

var sigHex = "3045022100f28c29042a6d766810e21f2c0a1839f93140989299cae1d37b49a454373659c802203d0967be0696686414fe2efed3a71bc1639d066ee127cfb7c0ad369521459d00"

var ec = new KJUR.crypto.ECDSA({'curve': 'secp256r1'})
var verified = ec.verifyHex(msgHashHex, sigHex, pubkeyHex)
console.log("Verification:", verified)
 
})();
Copy after login
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.4.0/jsrsasign-all-min.js"></script>
Copy after login

The above is the detailed content of Unable to verify Go generated ECDSA signature using JS. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Roblox: Bubble Gum Simulator Infinity - How To Get And Use Royal Keys
1 months ago By 尊渡假赌尊渡假赌尊渡假赌
Nordhold: Fusion System, Explained
1 months ago By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers Of The Witch Tree - How To Unlock The Grappling Hook
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

Java Tutorial
1677
14
PHP Tutorial
1279
29
C# Tutorial
1257
24
How to apply for Huobi Huobi API interface_Detailed explanation of Huobi Huobi API interface application process How to apply for Huobi Huobi API interface_Detailed explanation of Huobi Huobi API interface application process May 15, 2025 pm 03:54 PM

Applying for the Huobi API interface requires six steps: 1. Register a Huobi account and select "Global Station"; 2. Complete real-name authentication (L1/L2/L3); 3. Log in and enter the API management page; 4. Create an API key, fill in basic information and select permissions; 5. Generate and save Access Key and Secret Key; 6. Integrate API to the application, pay attention to security matters and change the key regularly.

Binance Coin Trend Prediction: How High Will BNB Go? Binance Coin Trend Prediction: How High Will BNB Go? May 15, 2025 am 11:21 AM

From the early stages to the present, BNB has shown significant growth. Although its value continues to rise, people have asked what heights BNB can reach. This guide will explore factors that affect the price of Binance Coin. We will discuss its recent performance and make predictions for the next few years. Binance Coin Trend Prediction: How high will BNB rise? What is BNB? BNB is a cryptocurrency of Binance Exchange (official registration) (official download), and Binance is the world's leading cryptocurrency trading platform. It debuted on Ethereum as an ERC-20 token in 2017 and moved to Binance Chain in 2019. Initially, BNB was designed for Binance

Vaneck launches its first RWA tokenized fund for institutional investors Vaneck launches its first RWA tokenized fund for institutional investors May 15, 2025 am 11:51 AM

The VBILL Token Fund was launched on May 13, providing institutional investors with the opportunity to tokenize U.S. fiscal bills. Vaneck's new tagged fund will enable institutional investors to invest in U.S. Treasury bills. The VBILL Token Fund was launched on May 13 as the result of a cooperation with Securities, a tokenized platform that supports the entire operation of the fund. The fund will be subscribed on four major blockchains – Ethereum, Sorana, Avalanche and BNB Chain. On three of these blockchains (Avalanche, Solana and BNB chains), the minimum subscription is $100,000. On Ethereum, the minimum investment is US$1 million. The fund is Vaneck's efforts to expand its real-life

Binance official latest v2.99.5 mobile version installation and registration tutorial Binance official latest v2.99.5 mobile version installation and registration tutorial May 15, 2025 pm 02:51 PM

As the world's leading cryptocurrency trading platform, Binance's mobile app provides users with a convenient trading experience. Whether you are a novice or a veteran trader, Binance’s mobile app is ready to meet your needs. This tutorial will introduce in detail how to download and install the latest Binance v2.99.5 mobile application and complete the registration process. Please note that the download links provided in this article are all official links, and you can download them safely using the download links of this article, without worrying about security issues.

What is the PWAR currency contract address? Introduction to PWAR currency contract address What is the PWAR currency contract address? Introduction to PWAR currency contract address May 15, 2025 pm 12:36 PM

Each investment team has shown great interest and enthusiasm for the project’s highly anticipated NFT Gaming multiverse, and they are still eager to support PolkaWar’s future in the cryptocurrency space. PolkaWar expects institutions and organized funds to have greater enthusiasm for early stage investment, and the company thanks everyone for their sponsorship and support to date. Funds raised will provide the ability to quickly deliver the roadmap and deploy projects in a shorter time. PolkaWar has unique advantages that can benefit from the upcoming surge and excitement. Investors want to know what the PWAR currency contract address is? Let me introduce the PWAR coins to you

What are XploraDEX and $XPL tokens? An article introduction What are XploraDEX and $XPL tokens? An article introduction May 15, 2025 am 11:45 AM

In the rapidly developing world of decentralized finance (DeFi), innovation never stops. The latest wave is not just about speed or safety – it is about wisdom. This is exactly when XploraDEX comes into the stage. XploraDEX, built on the XRP record, is setting new benchmarks for decentralized exchanges by introducing artificial intelligence (AI) to every level of trading. But what exactly is XploraDEX, how is it different from other platforms, and what role the $XPL token plays in its ecosystem? Let's parse step by step in an easy-to-understand way.

$500 to $700,000? Web3 AI is on track to bring millionaires as hypermobility and BGB surge $500 to $700,000? Web3 AI is on track to bring millionaires as hypermobility and BGB surge May 15, 2025 pm 12:21 PM

Ultra-liquidity (HYPE) price update shows that the Bulls are targeting $25 after public interest soared to a record $697 million. Super Liquidity (HYPE) Bulls targeted $25 as token trading is higher than the main support at $19.24. Open interest soared to a record $697 million, indicating strong momentum. Technical signals from RSI (14) and MACD (12,26) show that there may be more room for upwards. By 2030, the BitGet Token (BGB) price forecast targets $25 as exchanges expand and burn tokens worldwide. Web3AI's pre-sales are starting to attract attention, starting at just $0.03 and offering 1,333 times

Humo Token, backed by government bonds, is being tested in Uzbekistan (Uzdaily.com) (Uzdaily.com) Humo Token, backed by government bonds, is being tested in Uzbekistan (Uzdaily.com) (Uzdaily.com) May 15, 2025 pm 02:03 PM

Uzbekistan is experimenting with a new digital asset, Humo tokens secured by government bonds. The token is pegged to the national currency, and 1 Humo equals 1000 sum. The project is under implementation under Uzbekistan’s legal framework in the field of crypto assets. Several strategic partners have participated in its development, including the Humo payment system that serves 35 million cardholders in Uzbekistan. Thanks to Humo's extensive cooperation with commercial banks, markets and retail structures, the conditions have been created for the widespread use of tokens in daily transactions. The technical basis of the project is provided by Asterium and Broxus. The project adopts the Tycho blockchain protocol developed by Broxus. Its characteristics are high transaction speed and low transaction

See all articles