Я закодировал функции ECDSA в своей программе на C++. Когда я проверяю свою подпись, она отлично работает в моей функции проверки C++ ECDSA, но не каждый тест проходит на языке GO.
Поэтому я попытался экспортировать свой открытый ключ (из моего C++) в виде шестнадцатеричной строки буфера (33 байта) в язык GO. Затем я использую ellipitc.UnmarshalCompressed, чтобы получить свой открытый ключ в программе GO. Позже я обнаружил, что открытый ключ Unmarshal имеет другую координату Y, когда открытый ключ генерируется из секретного ключа с использованием скалярного умножения. Я поставил код ниже.
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"encoding/hex"
"fmt"
"log"
"math/big"
"os"
)
func main() {
/* UnmarshalCompressed public key from C++ buffer (hex string) */
publicKeyBufferFromCplusplus, err := hex.DecodeString("02d36b0e521ca9a28cd6f2ddc56dc0973215702f6f67ed0670b9bc9a98c28d473b")
if err != nil {
fmt.Println("Unable to convert hex to byte. ", err)
}
pk := new(ecdsa.PublicKey)
pk.Curve = elliptic.P256()
pk.X, pk.Y = elliptic.UnmarshalCompressed(elliptic.P256(), publicKeyBufferFromCplusplus[:])
/* Generate the key pair in GO, using the private key (as decimal) from C++ */
expect_sk := new(ecdsa.PrivateKey)
expect_sk.D, _ = new(big.Int).SetString("50228957095953179898827503463423289296009712707225507368245266147079499081684", 10)
expect_sk.PublicKey.Curve = elliptic.P256()
expect_sk.PublicKey.X, expect_sk.PublicKey.Y = expect_sk.PublicKey.Curve.ScalarBaseMult(expect_sk.D.Bytes())
expect_pk := expect_sk.PublicKey
/* compare the two public keys, the X coordinate is the same, but Y is different */
fmt.Printf("pk_x:\t\t%d\n", pk.X)
fmt.Printf("expect pk_x:\t%d\n\n", expect_pk.X)
fmt.Printf("pk_y:\t\t%d\n", pk.Y)
fmt.Printf("expect pk_y:\t%d\n", expect_pk.Y)
}
Вот результат из терминала
pk_x: 95627162525183504786576659676808415919520991299985517290103803735976207796027
expect pk_x: 95627162525183504786576659676808415919520991299985517290103803735976207796027
pk_y: 106312815215663533204607583749797836088594130128596587441436180287153537381066
expect pk_y: 9479273994692715558089863199609737441492013286693726754097451021713560472885
Обратите внимание, что разница заключается в координате Y, тогда как координата X одинакова.
Таким образом, проблема, скорее всего, не вызвана кодом Go, которого недостаточно для ответа на вопрос. Вы должны опубликовать код C++, используемый для создания второй пары ключей, а также третьей и четвертой подписи. А также закрытый ключ, принадлежащий второму открытому ключу (или, если вы не можете опубликовать это, полный набор новых тестовых данных).
Прямо сейчас кажется, что моя координата y открытого ключа несовместима со значением, используемым для подписи подписи.
Возможно, но в вашем вопросе не хватает информации, чтобы проверить это. Для этого потребуется закрытый ключ, а чтобы найти причину, потребуется код C++ для генерации и подписи ключа.
@Topaco Я обновил вопрос. Кажется, есть разница между генерацией открытого ключа из скалярного умножения и UnmarshalCompress из буфера открытого ключа.
Открытый ключ, связанный с вашим закрытым ключом, имеет нечетную букву y. Следовательно, начальный байт маркера в сжатом ключе должен иметь значение 0x03 вместо 0x02. Если вы исправите это (например, 0x03d36b0e521ca9a28cd6f2ddc56dc0973215702f6f67ed0670b9bc9a98c28d473b), будет определено правильное значение y.
Кстати, это новый вопрос, и его нужно было опубликовать как таковой. По крайней мере, новый код должен был быть добавлен, а старый не перезаписан. Последующие читатели вряд ли смогут за этим уследить!

Обновлять:
Я обнаружил, что в моей библиотеке эллиптических кривых C++ есть ошибка при сжатии (упорядочивании) открытого ключа (точек) в форму SEC 1, версия 2.0, раздел 2.3.3. В некоторых точках первый байт неправильный (от нечетного к четному или от четного к нечетному).
1*G GO buffer: 0x036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 C++ buffer: 0x026b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
2*G GO buffer: 0x037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978 C++ buffer: 0x037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978
3*G GO buffer: 0x025ecbe4d1a6330a44c8f7ef951d4bf165e6c6b721efada985fb41661bc6e7fd6c C++ buffer: 0x035ecbe4d1a6330a44c8f7ef951d4bf165e6c6b721efada985fb41661bc6e7fd6c
4*G GO buffer: 0x02e2534a3532d08fbba02dde659ee62bd0031fe2db785596ef509302446b030852 C++ buffer: 0x02e2534a3532d08fbba02dde659ee62bd0031fe2db785596ef509302446b030852
5*G GO buffer: 0x0251590b7a515140d2d784c85608668fdfef8c82fd1f5be52421554a0dc3d033ed C++ buffer: 0x0251590b7a515140d2d784c85608668fdfef8c82fd1f5be52421554a0dc3d033ed
6*G GO buffer: 0x02b01a172a76a4602c92d3242cb897dde3024c740debb215b4c6b0aae93c2291a9 C++ buffer: 0x03b01a172a76a4602c92d3242cb897dde3024c740debb215b4c6b0aae93c2291a9
7*G GO buffer: 0x028e533b6fa0bf7b4625bb30667c01fb607ef9f8b8a80fef5b300628703187b2a3 C++ buffer: 0x038e533b6fa0bf7b4625bb30667c01fb607ef9f8b8a80fef5b300628703187b2a3
8*G GO buffer: 0x0262d9779dbee9b0534042742d3ab54cadc1d238980fce97dbb4dd9dc1db6fb393 C++ buffer: 0x0362d9779dbee9b0534042742d3ab54cadc1d238980fce97dbb4dd9dc1db6fb393
9*G GO buffer: 0x02ea68d7b6fedf0b71878938d51d71f8729e0acb8c2c6df8b3d79e8a4b90949ee0 C++ buffer: 0x02ea68d7b6fedf0b71878938d51d71f8729e0acb8c2c6df8b3d79e8a4b90949ee0
Поэтому некоторые открытые ключи (точки) экспортируются как {x,-y} вместо {x,y}.
Оригинал
Открытый ключ на эллиптической кривой (EC) соответствует Стандартам эффективной криптографии (SEC), SEC 1: Криптография на эллиптических кривых в разделе 2.3.3 Преобразование точек на эллиптических кривых в строки октетов.
Открытый ключ в сжатой форме (в виде байтов) имеет первый байт как 0x02, если координата y четная. В противном случае 0x03, если координата y нечетная.
Математически, учитывая только координату x
y^2 = x^3 + ax + b (mod p)
У приведенного выше уравнения есть два решения: (x, y) и (x, -y), поскольку y^2 = (-y)^2
Кроме того, -y равно (p - y), так как это поле целых чисел по простому модулю p.
Поскольку p простое число и p!=2, p нечетно. Тогда, если y нечетно, то -y (или (p-y)) четно, и наоборот.
Алгоритм проверки ECDSA не требует координаты y открытого ключа, поскольку ему требуется только его координата x. Поэтому, когда я проверяю кортеж подписи {r,s}, я буду сверять его с открытым ключом с той же координатой x, но с другой координатой y (переворачивать с нечетного на четное или наоборот). Я действительно не знаю, почему пакет GO Language ECDSA допускает только одно из двух значений координаты y.
Вот мой пример кода ниже.
Допустим, у меня есть сжатый публичный ключ в качестве буфера publicKeyBuffer[:]
Затем я могу Разархивировать его, чтобы получить координаты x и y.
pk := new(ecdsa.PublicKey)
pk.Curve = elliptic.P256()
pk.X, pk.Y = elliptic.UnmarshalCompressed(elliptic.P256(), publicKeyBuffer[:])
Мы можем проверить, является ли кортеж подписи {r,s} допустимым или нет.
valid := ecdsa.Verify(pk, hash[:], r, s)
Если подпись недействительна, т. Е. valid == false, попробуйте изменить значение pk.Y на его аналог. Вы можете сделать это двумя простыми способами.
В первом случае вы меняете первый байт буфера открытого ключа с 0x02 на 0x03 или наоборот. Вы можете добиться этого, выполнив XOR первый байт с 0x01 (просто 1).
valid = ecdsa.Verify(pk, hash[:], r, s)
/* The 1st way */
if !valid {
publicKeyBuffer[0] ^= 1 // change the y-coordinate by switching 0x02 to 0x03 (or vice versa) of public key buffer
pk.X, pk.Y = elliptic.UnmarshalCompressed(elliptic.P256(), publicKeyBuffer[:])
valid = ecdsa.Verify(pk, hash[:], r, s)
}
fmt.Println("signature verified:", valid)
2-м способом мы меняем координату y на ее (p-y) значение, т.е. меняем ее на (-y)
valid = ecdsa.Verify(pk, hash[:], r, s)
/* The 2nd way */
if !valid {
pk.Y = new(big.Int).Sub(pk.Curve.Params().P, pk.Y) // replace y-coordinate pk.Y with (-y), Note. (-y) is equal (p - y)
valid = ecdsa.Verify(pk, hash[:], r, s)
}
fmt.Println("signature verified:", valid)
Если подпись все же недействительна, то дело не в открытом ключе Unmarshal.
P.S. Я не знаю, почему язык GO ECDSA не допускает другое значение координаты y открытого ключа.
Потому что, если P=(x,y) — ваш открытый ключ, то -P=(x,-y) — не ваш открытый ключ. Кроме того, при использовании -P проверка должна быть прекращена как недействительная подпись.
Я действительно не знаю, почему пакет GO Language ECDSA допускает только одно из двух значений координаты y: у меня сложилось впечатление, что вы допускаете двусмысленность там, где ее нет. При создании подписи открытый ключ, используемый для проверки, однозначен. Открытый ключ может быть указан в несжатом формате 0x04|x|y или в сжатом формате m|x (где m = 0x02 для четного y и 0x03 для нечетного y). Опять же, никакой двусмысленности. Так что вам никогда не придется искать правильный y методом проб и ошибок.
Это может быть необходимо только в том случае, если начальный байт маркера m установлен неправильно (т. е. если сжатый ключ был определен неправильно). Это действительно имело место в вашем опубликованном примере, s. этот комментарий . К сожалению, это больше не ясно из текущего вопроса, поскольку вы продолжаете перезаписывать содержимое вместо того, чтобы добавлять изменения (таким образом, см. историю , версия 2).
Возможно, есть ошибка в коде C++ для экспорта ключа (например, при получении сжатого ключа).
@Topaco В моей библиотеке эллиптических кривых C++ есть ошибка, которая используется для маршалинга (сжатия) ключа, как вы упомянули.
Перекрестная проверка с кодом Python подтверждает результат кода Go. Второй открытый ключ действителен для P-256 (он же secp256r1), но не может проверить 3-ю и 4-ю подписи. Возможно, эти подписи были созданы с помощью закрытого ключа, не связанного с открытым, или были каким-то образом повреждены.