Skip to content

Conversation

Xe
Copy link
Contributor

@Xe Xe commented Apr 9, 2025

This is for the pivot strategy to accomplish #94. Overall the strategy looks like this:

  1. Port existing checker to WebAssembly
  2. Hook it into the existing flow/logic
  3. Switch default checker to use this new webassembly-based flow
  4. Implement a small set of other checkers (equi-x, etc) and randomly pick between them based on bytes in the challenge.

NOTE: this changes the proof of work calculation logic for the new checker.

This differs from the JavaScript implementations by constructing the hash differently. In JavaScript implementations, the SHA-256 input is the result of appending the nonce as an integer to the hex-formatted challenge, eg:

sha256(`${challenge}${nonce}`);

This does work, however I think that this can be done a bit better by operating on the challenge bytes directly and treating the nonce as a salt.

The nonce is also randomly encoded in either big or little endian depending on the last byte of the data buffer in an effort to make it more annoying to automate with GPUs.

@Xe Xe added this to the v1.17.0: Varis zos Galvus milestone Apr 9, 2025
@Xe Xe requested a review from Copilot April 9, 2025 13:34
@Xe Xe self-assigned this Apr 9, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 18 out of 22 changed files in this pull request and generated 1 comment.

Files not reviewed (4)
  • go.mod: Language not supported
  • package.json: Language not supported
  • wasm/pow/sha256/run.html: Language not supported
  • web/static/wasm/.gitignore: Language not supported
Comments suppressed due to low confidence (1)

web/js/xeact.mjs:6

  • [nitpick] The function name 'u' is ambiguous; consider renaming it to a more descriptive name such as 'buildUrl' or 'generateUrl'.
export const u = (url = "", params = {}) => {

// is not prime, only some of the threads will be sending the status
// update and they will get behind the others. this is slightly more
// complicated but ensures an even distribution between threads.
if nonce > old_nonce | 1023 && (nonce >> 10) % iterand == initial_nonce {
Copy link

Copilot AI Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of the bitwise OR operator ('|') in 'old_nonce | 1023' appears unintended; if the intent was to add 1023, replace '|' with '+' to correctly calculate the threshold for sending a progress update.

Suggested change
if nonce > old_nonce | 1023 && (nonce >> 10) % iterand == initial_nonce {
if nonce > old_nonce + 1023 && (nonce >> 10) % iterand == initial_nonce {

Copilot uses AI. Check for mistakes.

Signed-off-by: Xe Iaso <[email protected]>
@max-te
Copy link

max-te commented Apr 12, 2025

Hi, so I have implemented something like this with Rust and WASM before for proof-of-work on login requests. That code lives in my private repo, but I've contributed some work back to the sha2 crate: If you want to use the SHA2 family of hashes, I recommend you check out the 0.11 pre-release version of the sha2 crate and compile the wasm with RUSTFLAG=-C target-feature=+simd128. This activates the wasm32 simd backend which gives around an 8x speed-up..

@Xe
Copy link
Contributor Author

Xe commented Apr 12, 2025

I'm gong to have to see if wazero supports SIMD, if it does then this will be an obvious addition. Thanks!

@Xe
Copy link
Contributor Author

Xe commented Apr 12, 2025

go tool benchcmp before-simd.txt after-simd.txt 
benchcmp is deprecated in favor of benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
benchmark              old ns/op     new ns/op     delta
BenchmarkSHA256-32     13222850      8209668       -37.91%

benchmark              old allocs     new allocs     delta
BenchmarkSHA256-32     85             85             +0.00%

benchmark              old bytes     new bytes     delta
BenchmarkSHA256-32     29838         23071         -22.68%

very yes, neat!

@Xe Xe marked this pull request as ready for review April 14, 2025 13:29
@Xe Xe requested a review from Copilot April 14, 2025 13:29
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 36 out of 39 changed files in this pull request and generated 4 comments.

Files not reviewed (3)
  • Makefile: Language not supported
  • go.mod: Language not supported
  • package.json: Language not supported

}

if !hasLeadingZeroNibbles(verify, difficulty) {
return false, fmt.Errorf("%w: wanted %d leading zeroes in verification data %x, but did not get it", ErrWrongChallengeDifficulty, verify, difficulty)
Copy link

Copilot AI Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message has the same argument order issue as above: the %d specifier is paired with 'verify' (a byte slice) instead of a numeric value. Swap 'difficulty' and 'verify' accordingly.

Suggested change
return false, fmt.Errorf("%w: wanted %d leading zeroes in verification data %x, but did not get it", ErrWrongChallengeDifficulty, verify, difficulty)
return false, fmt.Errorf("%w: wanted %d leading zeroes in verification data %x, but did not get it", ErrWrongChallengeDifficulty, difficulty, verify)

Copilot uses AI. Check for mistakes.

Co-authored-by: Copilot <[email protected]>
Signed-off-by: Xe Iaso <[email protected]>
Comment on lines +49 to +69
count := uint32(0)
for _, b := range data {
// Check high nibble (first 4 bits)
if (b >> 4) != 0 {
break // Non-zero found in leading nibbles
}
count++
if count >= n {
return true
}

// Check low nibble (last 4 bits)
if (b & 0x0F) != 0 {
break // Non-zero found in leading nibbles
}
count++
if count >= n {
return true
}
}
return count >= n
Copy link

@nicoonoclaste nicoonoclaste Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the length is constant and a multiple of 64b, wouldn't that simplify down to something like this?

count := uint32(0)
for _, w := range slices.Chunk(data, 8) {
  d := binary.BigEndian.Uint64(w)
  count += bits.LeadingZeros64(d) / 4
  if d != 0 {
    break
  }
}
return count

(using slices.Chunk to make it more readable, but of course that could be done by a project-local function to avoid that dependency)

PS: Please excuse the approximate Go, I seldom write that language 😅

/// Core validation function. Compare each bit in the hash by progressively masking bits until
/// some are found to not be matching.
///
/// There are probably more clever ways to do this, likely involving lookup tables or something

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use the Count Leading Zeros intrinsic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants