-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement go bindings to secp256k1 #1
Conversation
git-subtree-dir: depend/secp256k1 git-subtree-split: ee3ab072d625da9f3418f94c62ef9dcc1ff1be0c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do go mod init
.
Than run golint
and go vet
and fix all warnings they output (note: In some rare cases go vet
might have false positives. This is not something we have every encountered, but unlike with golint there's no guarantee against them. If you think it does output any false positives, let's discuss it)
multiset.go
Outdated
func (multiset *MultiSet) Reset() { | ||
ret := C.secp256k1_multiset_init(context, &multiset.set) | ||
if ret != 1 { | ||
panic("failed intializing a multiset. should never happen") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is reset, not initialize (I guess copy-paste error?)
multiset_test.go
Outdated
for _, test := range testVectors { | ||
data, err := hex.DecodeString(test.dataElementHex) | ||
if err != nil { | ||
t.Fatal(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In all Fatals - please include a short message explaining where the failure happened.
We have stack traces, but it's still very convenient when there's a short textual explanation on what has happened.
Note: good chance that after you execute the comment a few lines above, this will already be obsolete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure. FYI these are a copy-paste from: https://github.com/kaspanet/kaspad/blob/master/ecc/ecmh_test.go#L39
multiset_test.go
Outdated
} | ||
m1.Combine(m2) | ||
if m1.Finalize() != zeroHash { | ||
t.Fatalf("m1 was expected to return to have zero hash, but was %s instead", m1.Finalize()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammar: to return to have
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oldschnorr.go
Outdated
func (key *SchnorrPublicKey) Negate() { | ||
ret := C.secp256k1_ec_pubkey_negate(C.secp256k1_context_no_precomp, &key.pubkey) | ||
if ret != 1 { | ||
panic("Failed Negating the public key. should never happen") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error messages shouldn't start with capital letters
secp256k1.go
Outdated
@@ -0,0 +1,92 @@ | |||
package secp256k1 | |||
|
|||
// // **This is CGO's build system. yes, in comments.** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Complaining about the language's quirks in the code is very understandable, but undesirable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's both complaining and being highly explicit that these are not comments. suggested language?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about something like:
"CGO defs. CGO parses the following comments as build instructions."
} | ||
} | ||
|
||
type PrivateKey struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type PrivateKey [32]byte
is much more concise and nicer to use
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it might be a good idea to move PrivateKey/PublicKey and all related methods to a separate file/s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't known that type PrivateKey [32]byte
is a thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like you missed fixing these comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to not let users access the private key directly like that, the struct adds a visibility barrier (with no cost I assume).
And this actually increase the line count, because it requires me to assign temporaries before returning(because you can't take the address of an array without assigning it)
secp256k1_test.go
Outdated
"testing" | ||
) | ||
|
||
// TODO: Add test vectors. (even though they're included in libsecp itself) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too: no TODOs in the code, create a ticket instead
secp256k1_test.go
Outdated
|
||
const LoopsN = 150 | ||
|
||
var Secp256k1Order, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't ignore errors.
You can do initialization logic in TestMain instead.
What I'd do is have the string representation as a const, than have TestMain parse it and assign global var Secp256k1Order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about that vs doing this?
var Secp256k1Order = new(big.Int).SetBytes([]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 186, 174, 220, 230, 175, 72, 160, 59, 191, 210, 94, 140, 208, 54, 65, 65})
This is way longer and not readable, but it skips the need for TestMain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this can work. It is indeed ugly and not readible, but A) this is a test, so not as critical. and B) it's not as though the hexa was in any way more readable, just shorter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another note about govet/golint: we can help setup Jenkins to run this automatically on PRs, to make sure we are always clear.
secp256k1.go
Outdated
@@ -0,0 +1,92 @@ | |||
package secp256k1 | |||
|
|||
// // **This is CGO's build system. yes, in comments.** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about something like:
"CGO defs. CGO parses the following comments as build instructions."
oldschnorr.go
Outdated
} | ||
|
||
func isZeroed(slice []C.uchar) bool { | ||
for _, v := range slice { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid one-character names. "v" makes readers needlessly stop to think "wait, what's v? Is it something special?" for a moment before realizing that "oh yeah, it's just value"
secp256k1_test.go
Outdated
|
||
// TODO: Add test vectors. (even though they're included in libsecp itself) | ||
|
||
const LoopsN = 150 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name LoopsN is a bit unclear. Please either rename it or add a short explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, does LoopsN need to be exported? If you aren't using it anywhere outside of this package, please unexport it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a test variable for how many times to run the loops in the tests. it's used both in secp256k1_test and in multiset_test, I can put different ones in each file though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlike Rust, files don't define scope. Meaning that since both files are inside the package "secp256k1" if you unexport LoopsN here it will still be available in multiset_test.go.
secp256k1_test.go
Outdated
} | ||
} | ||
|
||
func TestPrivateKey_Add_fail(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please capitalize the f in fail.
secp256k1_test.go
Outdated
t.Errorf("A zeroed key is invalid") | ||
} | ||
if err4 != nil || err5 != nil { | ||
t.Errorf("It should be possible to add zero to a key") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check each error individually and add it to the error string here and everywhere. That way when the test fails we'll know exactly and immediately where it fails instead of having to connect a debugger.
secp256k1_test.go
Outdated
copy(msg32[:], test.message) | ||
valid := pubkey.SchnorrVerify(msg32, sig) | ||
if valid != test.valid { | ||
t.Errorf("Schnorr test vector %d didn't produce correct result", i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please specify in the error what was the result and what was the expected result.
secp256k1_test.go
Outdated
msg := [32]byte{} | ||
n, err := rand.Read(msg[:]) | ||
if err != nil || n != 32 { | ||
panic("benchmark failed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the error to the string.
multiset_test.go
Outdated
copy(data[:], Secp256k1Order.Bytes()) | ||
_, err := ParseMultiSet(data) | ||
if err == nil { | ||
t.Errorf("Shouldn't be able to parse a multiset bigger with x bigger than the field size") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the error itself to the string.
multiset_test.go
Outdated
list[i] = data | ||
} | ||
if set.Finalize() == set2.Finalize() { | ||
t.Errorf("sets are the same when they should be different: set %x\n", set.Finalize()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please print both sets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The if
only executes if they're the same though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, yeah, my bad.
I think I applied all your comments, you can review the diff here: https://github.com/kaspanet/go-secp256k1/compare/8cf663fb9f1b1d9b47af041bb487eae97c57ffe4..bca4c0d5a9eae14ddd24f4638bf83271df464495 |
multiset_test.go
Outdated
data := [100]byte{} | ||
n, err := rand.Read(data[:]) | ||
if err != nil || n != len(data) { | ||
t.Fatalf("Failed generating random data ''%v' ''%d' ", err, n) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please switch the order of err and n, and annotate n. Likewise in other tests.
multiset_test.go
Outdated
list[i] = data | ||
} | ||
if set.Finalize() == set2.Finalize() { | ||
t.Errorf("sets are the same when they should be different: set %x\n", set.Finalize()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, yeah, my bad.
multiset_test.go
Outdated
} | ||
parsedSet, err := ParseMultiSet(serializedEmpty) | ||
if err != nil { | ||
t.Errorf("error: '%v' happened when parsing: ''%x'", err, serializedEmpty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please put the error at the end of the string. This is because the error string might be quite long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Thanks.
a20b377
to
41cded5
Compare
multiset.go
Outdated
// which is a rolling(homomorphic) hash that you can add and remove elements from | ||
// and receiving the same resulting hash as-if you never hashed that element. | ||
// Because of that the order of adding and removing elements doesn't matter. | ||
// The type should only be created via the supplied methods. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd revise to something like "Use NewMultiset to initialize a MultiSet"
multiset_test.go
Outdated
panic("bad benchmark") | ||
} | ||
} | ||
if set == NewMultiset() { // To prevent optimizing out the loop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a real scenario that really happens?
If yes, please note that once you do that NewMultiset returns a pointer, you might had this functionality broken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I honestly don't know :)
I know this is in fact a problem I've encountered many times in C/C++ and Rust, don't know how aggressive is Go's optimizer and if it gives special treatment for benchmarks
Example: bitcoin-core/secp256k1#667 bitcoin-core/secp256k1#678
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmmm... I really doubt that's going to happen.
The benchmark loop is a standard golang construct. I have written a ton of benchmark this way and never encountered a similar problem.
I'd rather remove this piece of code, and re-introduce only if we find it's needed.
oldschnorr.go
Outdated
|
||
// SchnorrVerify verifies a schnorr signature using the public key and the input hashed message. | ||
// Notice: the [32] byte array *MUST* be a hash of a message you hashed yourself. | ||
func (key *SchnorrPublicKey) SchnorrVerify(hash [32]byte, signature SchnorrSignature) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add type Hash [32]byte
and use pointer to it instead of direct [32]byte
.
Also use it wherever ecmh returns a hash, and define similar type for ecmh's [64]byte serialization format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does that help/give?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Than you will be able to pass by reference easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
} | ||
|
||
type PrivateKey struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like you missed fixing these comments.
multiset.go
Outdated
|
||
// NewMultiset return an empty initialized set. | ||
// when finalized it should be equal to a finalized set with all elements removed. | ||
func NewMultiset() MultiSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should always be a pointer except for simple types (ints, bools, etc) or types that are reference-types already (strings, maps, slices, interfaces).
In other words - if it's a struct, you should probably pass it around as a pointer.
Indeed, in some cases deep examination would prove that some structs are better of passed-by-value. However, these are the kinds of micro-optimizations that we would rather refrain from, for the sake of simplicity.
That said, in cases where there's a proven bottleneck that would benefit a lot from passing something by-value, no doubt we would consider doing it. Nevertheless - the default go-to should be pass-by-pointer.
multiset.go
Outdated
|
||
// NewMultiset return an empty initialized set. | ||
// when finalized it should be equal to a finalized set with all elements removed. | ||
func NewMultiset() MultiSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note that all MultiSet functions have a pointer to MultiSet as the receiver.
If you wanted MultiSet to be a value-type for some reason, you would also want it's methods to have a value receiver)
multiset.go
Outdated
} | ||
} | ||
|
||
// Add a data element to the multiset. This will hash the data onto the curve and add it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments in go should be in the form "[Function name] [does blablabla]".
Yes, what you did satisfies the linter, because comment indeed starts with the name of the function, but it misses the point.
Please fix in all the codebase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the new ones ok? not the best in wording :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new ones are looking fine. Wording can be always improved, but it is absolutely fine.
multiset_test.go
Outdated
panic("bad benchmark") | ||
} | ||
} | ||
if set == NewMultiset() { // To prevent optimizing out the loop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmmm... I really doubt that's going to happen.
The benchmark loop is a standard golang construct. I have written a ton of benchmark this way and never encountered a similar problem.
I'd rather remove this piece of code, and re-introduce only if we find it's needed.
return key.privateKey | ||
} | ||
|
||
// Negate a private key in place. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is Negate needed?
Asking more out of curiosity than anything else
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it was needed for BIP32 but I think that's not true.
But I guess there are other more complex applications that require this bitcoin-core/secp256k1#408
secp256k1_test.go
Outdated
|
||
const LoopsN = 150 | ||
|
||
var Secp256k1Order, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this can work. It is indeed ugly and not readible, but A) this is a test, so not as critical. and B) it's not as though the hexa was in any way more readable, just shorter.
oldschnorr.go
Outdated
// DeserializeSchnorrSignature deserializes a 64 byte serialized schnorr signature into a SchnorrSignature type. | ||
func DeserializeSchnorrSignature(serializedSignature [64]byte) SchnorrSignature { | ||
signature := SchnorrSignature{} | ||
signature.signature = serializedSignature |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can write this whole function in one line return SchnorrSignature{signature:serializedSignature}
Should I just implement |
|
It is redundant in many cases, but for hardcore-library-level code that you are writing: |
Hi,
These are go bindings for the https://github.com/kaspanet/secp256k1 repository.
the repo is included as a subtree with the script
./depend/update-subtree.sh
to pull updates from the subtree.There are currently some integration problems into kaspad, mostly because kaspad does utxo multiset diffs. which aren't supported by the code, but this should probably be changed in kaspad if I understand things correctly.
As usual review is done best commit by commit :)